@Test(enabled = true)
 /** Tests approximation error. "enabled = false" for the standard testing. */
 public void errorAnalysis() {
   double bp1 = 10000;
   double errorLimit = 5.0E-1; // 0.5 bp
   ParRateCalculator prc = ParRateCalculator.getInstance();
   double forward = prc.visit(SWAP_PAYER, CURVES);
   double[] strikeRel = new double[] {-0.0250, -0.0150, -0.0050, 0.0, 0.0050, 0.0150, 0.0250};
   double[] pvPayerApproximation = new double[strikeRel.length];
   double[] pvPayerIntegration = new double[strikeRel.length];
   double[] pvReceiverApproximation = new double[strikeRel.length];
   double[] pvReceiverIntegration = new double[strikeRel.length];
   for (int loopstrike = 0; loopstrike < strikeRel.length; loopstrike++) {
     SwapFixedIborDefinition swapStrikePayerDefinition =
         SwapFixedIborDefinition.from(
             SETTLEMENT_DATE, CMS_INDEX, bp1, forward + strikeRel[loopstrike], FIXED_IS_PAYER);
     SwaptionCashFixedIborDefinition swaptionStrikePayerDefinition =
         SwaptionCashFixedIborDefinition.from(EXPIRY_DATE, swapStrikePayerDefinition, IS_LONG);
     SwaptionCashFixedIbor swaptionStrikePayer =
         swaptionStrikePayerDefinition.toDerivative(REFERENCE_DATE, CURVES_NAME);
     pvPayerApproximation[loopstrike] =
         METHOD_HW_APPROXIMATION.presentValue(swaptionStrikePayer, BUNDLE_HW).getAmount();
     pvPayerIntegration[loopstrike] =
         METHOD_HW_INTEGRATION.presentValue(swaptionStrikePayer, BUNDLE_HW).getAmount();
     assertEquals(
         "Swaption cash - Hull-White - present value - explicit/numerical integration",
         pvPayerApproximation[loopstrike],
         pvPayerIntegration[loopstrike],
         errorLimit);
     SwapFixedIborDefinition swapStrikeReceiverDefinition =
         SwapFixedIborDefinition.from(
             SETTLEMENT_DATE, CMS_INDEX, bp1, forward + strikeRel[loopstrike], !FIXED_IS_PAYER);
     SwaptionCashFixedIborDefinition swaptionStrikeReceiverDefinition =
         SwaptionCashFixedIborDefinition.from(EXPIRY_DATE, swapStrikeReceiverDefinition, IS_LONG);
     SwaptionCashFixedIbor swaptionStrikeReceiver =
         swaptionStrikeReceiverDefinition.toDerivative(REFERENCE_DATE, CURVES_NAME);
     pvReceiverApproximation[loopstrike] =
         METHOD_HW_APPROXIMATION.presentValue(swaptionStrikeReceiver, BUNDLE_HW).getAmount();
     pvReceiverIntegration[loopstrike] =
         METHOD_HW_INTEGRATION.presentValue(swaptionStrikeReceiver, BUNDLE_HW).getAmount();
     assertEquals(
         "Swaption cash - Hull-White - present value - explicit/numerical integration",
         pvReceiverApproximation[loopstrike],
         pvReceiverIntegration[loopstrike],
         errorLimit);
   }
 }
/**
 * Class used to compute the price of a CMS cap/floor by swaption replication on a SABR formula with
 * extrapolation. Reference: Hagan, P. S. (2003). Convexity conundrums: Pricing CMS swaps, caps, and
 * floors. Wilmott Magazine, March, pages 38--44. OpenGamma implementation note: Replication pricing
 * for linear and TEC format CMS, Version 1.2, March 2011. OpenGamma implementation note for the
 * extrapolation: Smile extrapolation, version 1.2, May 2011.
 *
 * @deprecated Use classes descended from {@link CapFloorCMSSABRReplicationAbstractMethod}
 */
@Deprecated
public class CapFloorCMSSABRExtrapolationRightReplicationMethod
    extends CapFloorCMSSABRReplicationAbstractMethod {

  /** The cut-off strike. The smile is extrapolated above that level. */
  private final double _cutOffStrike;
  /** The tail thickness parameter. */
  private final double _mu;

  /** The par rate calculator. */
  private static final ParRateCalculator PRC = ParRateCalculator.getInstance();
  /** The par rate sensitivity calculator. */
  private static final ParRateCurveSensitivityCalculator PRSC =
      ParRateCurveSensitivityCalculator.getInstance();

  /**
   * Default constructor of the CMS cap/floor replication method. The default integration interval
   * is 1.00 (100%).
   *
   * @param cutOffStrike The cut-off strike.
   * @param mu The tail thickness parameter.
   */
  public CapFloorCMSSABRExtrapolationRightReplicationMethod(
      final double cutOffStrike, final double mu) {
    super(1.0);
    _mu = mu;
    _cutOffStrike = cutOffStrike;
  }

  /**
   * Default constructor of the CMS cap/floor replication method. The default integration interval
   * is 1.00 (100%).
   *
   * @param cutOffStrike The cut-off strike.
   * @param mu The tail thickness parameter.
   * @param integrationInterval Integration range.
   */
  public CapFloorCMSSABRExtrapolationRightReplicationMethod(
      final double cutOffStrike, final double mu, final double integrationInterval) {
    super(integrationInterval);
    _mu = mu;
    _cutOffStrike = cutOffStrike;
  }

  /**
   * Compute the present value 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.
   * @return The present value.
   */
  @Override
  public CurrencyAmount presentValue(
      final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
    Validate.notNull(cmsCapFloor);
    Validate.notNull(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 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 CMSIntegrant integrant =
        new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
    final double strike = cmsCapFloor.getStrike();
    final double factor = discountFactorTp / integrant.h(forward) * integrant.g(forward);
    final double strikePart = factor * integrant.k(strike) * integrant.bs(strike);
    final double absoluteTolerance =
        1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
    final double relativeTolerance = 1E-10;
    final RungeKuttaIntegrator1D integrator =
        new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
    double integralPart;
    try {
      if (cmsCapFloor.isCap()) {
        integralPart =
            discountFactorTp
                * integrator.integrate(integrant, strike, strike + getIntegrationInterval());
      } else {
        integralPart = discountFactorTp * integrator.integrate(integrant, 0.0, strike);
      }
    } catch (final Exception e) {
      throw new RuntimeException(e);
    }
    final double priceCMS =
        (strikePart + integralPart)
            * cmsCapFloor.getNotional()
            * cmsCapFloor.getPaymentYearFraction();
    return CurrencyAmount.of(cmsCapFloor.getCurrency(), priceCMS);
  }

  @Override
  public CurrencyAmount presentValue(
      final InstrumentDerivative instrument, final YieldCurveBundle curves) {
    Validate.isTrue(instrument instanceof CapFloorCMS, "CMS cap/floor");
    Validate.isTrue(
        curves instanceof SABRInterestRateDataBundle, "Bundle should contain SABR data");
    return presentValue((CapFloorCMS) instrument, (SABRInterestRateDataBundle) curves);
  }

  /**
   * Computes the present value sensitivity to the yield curves of a CMS cap/floor by replication in
   * the 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 curves.
   */
  @Override
  public InterestRateCurveSensitivity presentValueCurveSensitivity(
      final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
    Validate.notNull(cmsCapFloor);
    Validate.notNull(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);
    // Common
    final CMSIntegrant integrantPrice =
        new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
    final CMSDeltaIntegrant integrantDelta =
        new CMSDeltaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
    final double factor = discountFactor / integrantDelta.h(forward) * integrantDelta.g(forward);
    final double absoluteTolerance =
        1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
    final double relativeTolerance = 1E-10;
    final RungeKuttaIntegrator1D integrator =
        new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
    // Price
    final double[] bs = integrantDelta.bsbsp(strike);
    @SuppressWarnings("synthetic-access")
    final double[] n = integrantDelta.nnp(forward);
    final double strikePartPrice = discountFactor * integrantDelta.k(strike) * n[0] * bs[0];
    double integralPartPrice;
    try {
      if (cmsCapFloor.isCap()) {
        integralPartPrice =
            discountFactor
                * integrator.integrate(integrantPrice, strike, strike + getIntegrationInterval());
      } else {
        integralPartPrice = discountFactor * integrator.integrate(integrantPrice, 0.0, strike);
      }
    } catch (final Exception e) {
      throw new RuntimeException(e);
    }
    final double price =
        (strikePartPrice + integralPartPrice)
            * cmsCapFloor.getNotional()
            * cmsCapFloor.getPaymentYearFraction();
    // Delta
    final double strikePart =
        discountFactor * integrantDelta.k(strike) * (n[1] * bs[0] + n[0] * bs[1]);
    double integralPart;
    try {
      if (cmsCapFloor.isCap()) {
        integralPart =
            discountFactor
                * integrator.integrate(integrantDelta, strike, strike + getIntegrationInterval());
      } else {
        integralPart = discountFactor * integrator.integrate(integrantDelta, 0.0, strike);
      }
    } catch (final Exception e) {
      throw new RuntimeException(e);
    }
    final double deltaS0 =
        (strikePart + integralPart)
            * cmsCapFloor.getNotional()
            * cmsCapFloor.getPaymentYearFraction();
    final double deltaPD = price / discountFactor;
    final double sensiDF = -cmsCapFloor.getPaymentTime() * discountFactor * deltaPD;
    final List<DoublesPair> list = new ArrayList<>();
    list.add(new DoublesPair(cmsCapFloor.getPaymentTime(), sensiDF));
    final Map<String, List<DoublesPair>> resultMap = new HashMap<>();
    resultMap.put(
        cmsCapFloor.getUnderlyingSwap().getFixedLeg().getNthPayment(0).getFundingCurveName(), list);
    InterestRateCurveSensitivity result = new InterestRateCurveSensitivity(resultMap);
    final InterestRateCurveSensitivity forwardDr =
        new InterestRateCurveSensitivity(cmsCapFloor.getUnderlyingSwap().accept(PRSC, sabrData));
    result = result.plus(forwardDr.multipliedBy(deltaS0));
    return result;
  }

  /**
   * 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;
  }

  /**
   * 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);
  }

  /** Inner class to implement the integration used in price replication. */
  private class CMSIntegrant extends Function1D<Double, Double> {
    protected static final double EPS = 1E-10;
    private final int _nbFixedPeriod;
    private final int _nbFixedPaymentYear;
    private final double _tau;
    private final double _delta;
    private final double _eta;
    private final double _timeToExpiry;
    private final double _strike;
    private final double _forward;
    private final double _factor;
    private final SABRExtrapolationRightFunction _sabrExtrapolation;
    private final boolean _isCall;

    /**
     * Gets the _nbFixedPeriod field.
     *
     * @return the _nbFixedPeriod
     */
    public int getNbFixedPeriod() {
      return _nbFixedPeriod;
    }

    /**
     * Gets the _nbFixedPaymentYear field.
     *
     * @return the _nbFixedPaymentYear
     */
    public int getNbFixedPaymentYear() {
      return _nbFixedPaymentYear;
    }

    /**
     * Gets the _tau field.
     *
     * @return the _tau
     */
    public double getTau() {
      return _tau;
    }

    /**
     * Gets the _eta field.
     *
     * @return the _eta
     */
    public double getEta() {
      return _eta;
    }

    /**
     * Gets the _timeToExpiry field.
     *
     * @return the _timeToExpiry
     */
    public double getTimeToExpiry() {
      return _timeToExpiry;
    }

    /**
     * Gets the _sabrExtrapolation field.
     *
     * @return the _sabrExtrapolation
     */
    public SABRExtrapolationRightFunction getSabrExtrapolation() {
      return _sabrExtrapolation;
    }

    /**
     * Gets the _isCall field.
     *
     * @return the _isCall
     */
    public boolean isCall() {
      return _isCall;
    }

    /**
     * Gets the _strike field.
     *
     * @return the _strike
     */
    public double getStrike() {
      return _strike;
    }

    /**
     * Constructor.
     *
     * @param cmsCap The CMS cap/floor.
     * @param sabrParameter The SABR parameters.
     * @param forward The forward.
     * @param cutOffStrike The cut-off strike.
     * @param mu The tail thickness parameter.
     */
    public CMSIntegrant(
        final CapFloorCMS cmsCap,
        final SABRFormulaData sabrPoint,
        final double forward,
        final double cutOffStrike,
        final double mu) {
      _nbFixedPeriod = cmsCap.getUnderlyingSwap().getFixedLeg().getPayments().length;
      _nbFixedPaymentYear =
          (int)
              Math.round(
                  1.0
                      / cmsCap
                          .getUnderlyingSwap()
                          .getFixedLeg()
                          .getNthPayment(0)
                          .getPaymentYearFraction());
      _tau = 1.0 / _nbFixedPaymentYear;
      _delta = cmsCap.getPaymentTime() - cmsCap.getSettlementTime();
      _eta = -_delta;
      _timeToExpiry = cmsCap.getFixingTime();
      _forward = forward;
      _sabrExtrapolation =
          new SABRExtrapolationRightFunction(forward, sabrPoint, cutOffStrike, _timeToExpiry, mu);
      _isCall = cmsCap.isCap();
      _strike = cmsCap.getStrike();
      _factor = g(_forward) / h(_forward);
    }

    @Override
    public Double evaluate(final Double x) {
      final double[] kD = kpkpp(x);
      // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative
      // of k.
      return _factor * (kD[1] * (x - _strike) + 2.0 * kD[0]) * bs(x);
    }

    /**
     * The approximation of the discount factor as function of the swap rate.
     *
     * @param x The swap rate.
     * @return The discount factor.
     */
    double h(final double x) {
      return Math.pow(1.0 + _tau * x, _eta);
    }

    /**
     * The cash annuity.
     *
     * @param x The swap rate.
     * @return The annuity.
     */
    double g(final double x) {
      if (x >= EPS) {
        final double periodFactor = 1 + x / _nbFixedPaymentYear;
        final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod);
        return 1.0 / x * (1.0 - nPeriodDiscount);
      }
      return ((double) _nbFixedPeriod) / _nbFixedPaymentYear;
    }

    /**
     * The factor used in the strike part and in the integration of the replication.
     *
     * @param x The swap rate.
     * @return The factor.
     */
    double k(final double x) {
      double g;
      double h;
      if (x >= EPS) {
        final double periodFactor = 1 + x / _nbFixedPaymentYear;
        final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod);
        g = 1.0 / x * (1.0 - nPeriodDiscount);
        h = Math.pow(1.0 + _tau * x, _eta);
      } else {
        g = ((double) _nbFixedPeriod) / _nbFixedPaymentYear;
        h = 1.0;
      }
      return h / g;
    }

    /**
     * The first and second derivative of the function k.
     *
     * @param x The swap rate.
     * @return The derivative (first element is the first derivative, second element is second
     *     derivative.
     */
    protected double[] kpkpp(final double x) {
      final double periodFactor = 1 + x / _nbFixedPaymentYear;
      final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod);
      /** The value of the annuity and its first and second derivative. */
      double g, gp, gpp;
      if (x >= EPS) {
        g = 1.0 / x * (1.0 - nPeriodDiscount);
        gp = -g / x + _nbFixedPeriod / x / _nbFixedPaymentYear * nPeriodDiscount / periodFactor;
        gpp =
            2.0 / (x * x) * g
                - 2.0
                    * _nbFixedPeriod
                    / (x * x)
                    / _nbFixedPaymentYear
                    * nPeriodDiscount
                    / periodFactor
                - (_nbFixedPeriod + 1.0)
                    * _nbFixedPeriod
                    / x
                    / (_nbFixedPaymentYear * _nbFixedPaymentYear)
                    * nPeriodDiscount
                    / (periodFactor * periodFactor);
      } else {
        // Implementation comment: When x is (almost) 0, useful for CMS swaps which are priced as
        // CMS cap of strike 0.
        g = ((double) _nbFixedPeriod) / _nbFixedPaymentYear;
        gp =
            -_nbFixedPeriod
                / 2.0
                * (_nbFixedPeriod + 1.0)
                / (_nbFixedPaymentYear * _nbFixedPaymentYear);
        gpp =
            _nbFixedPeriod
                / 2.0
                * (_nbFixedPeriod + 1.0)
                * (1.0 + (_nbFixedPeriod + 2.0) / 3.0)
                / (_nbFixedPaymentYear * _nbFixedPaymentYear * _nbFixedPaymentYear);
      }
      final double h = Math.pow(1.0 + _tau * x, _eta);
      final double hp = _eta * _tau * h / periodFactor;
      final double hpp = (_eta - 1.0) * _tau * hp / periodFactor;
      final double kp = hp / g - h * gp / (g * g);
      final double kpp =
          hpp / g - 2 * hp * gp / (g * g) - h * (gpp / (g * g) - 2 * (gp * gp) / (g * g * g));
      return new double[] {kp, kpp};
    }

    /**
     * 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);
    }
  }

  private class CMSDeltaIntegrant extends CMSIntegrant {

    private final double[] _nnp;

    public CMSDeltaIntegrant(
        final CapFloorCMS cmsCap,
        final SABRFormulaData sabrPoint,
        final double forward,
        final double cutOffStrike,
        final double mu) {
      super(cmsCap, sabrPoint, forward, cutOffStrike, mu);
      _nnp = nnp(forward);
    }

    @Override
    public Double evaluate(final Double x) {
      final double[] kD = kpkpp(x);
      // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative
      // of k.
      final double[] bs = bsbsp(x);
      return (kD[1] * (x - getStrike()) + 2.0 * kD[0]) * (_nnp[1] * bs[0] + _nnp[0] * bs[1]);
    }

    /**
     * The Black-Scholes formula and its derivative with respect to the forward.
     *
     * @param strike The strike.
     * @return The Black-Scholes formula and its derivative.
     */
    double[] bsbsp(final double strike) {
      final double[] result = new double[2];
      final EuropeanVanillaOption option =
          new EuropeanVanillaOption(strike, getTimeToExpiry(), isCall());
      result[0] = getSabrExtrapolation().price(option);
      result[1] = getSabrExtrapolation().priceDerivativeForward(option);
      return result;
    }

    private double[] nnp(final double x) {
      final double[] result = new double[2];
      final double[] ggp = ggp(x);
      final double[] hhp = hhp(x);
      result[0] = ggp[0] / hhp[0];
      result[1] = ggp[1] / hhp[0] - ggp[0] * hhp[1] / (hhp[0] * hhp[0]);
      return result;
    }

    private double[] ggp(final double x) {
      final double[] result = new double[2];
      if (x >= EPS) {
        final double periodFactor = 1 + x / getNbFixedPaymentYear();
        final double nPeriodDiscount = Math.pow(periodFactor, -getNbFixedPeriod());
        result[0] = 1.0 / x * (1.0 - nPeriodDiscount);
        result[1] =
            -result[0] / x + getTau() * getNbFixedPeriod() / x * nPeriodDiscount / periodFactor;
      } else {
        result[0] = getNbFixedPeriod() * getTau();
        result[1] = -getNbFixedPeriod() * (getNbFixedPeriod() + 1.0) * getTau() * getTau() / 2.0;
      }
      return result;
    }

    private double[] hhp(final double x) {
      final double[] result = new double[2];
      result[0] = Math.pow(1.0 + getTau() * x, getEta());
      result[1] = getEta() * getTau() * result[0] / (1 + x * getTau());
      return result;
    }
  }

  private class CMSVegaIntegrant extends CMSIntegrant {

    /** The index of the sensitivity computed. */
    private int _parameterIndex;

    /**
     * Constructor.
     *
     * @param cmsCap The CMS cap/floor.
     * @param sabrParameter The SABR parameters.
     * @param forward The forward.
     * @param cutOffStrike The cut-off strike.
     * @param mu The tail thickness parameter.
     */
    public CMSVegaIntegrant(
        final CapFloorCMS cmsCap,
        final SABRFormulaData sabrPoint,
        final double forward,
        final double cutOffStrike,
        final double mu) {
      super(cmsCap, sabrPoint, forward, cutOffStrike, mu);
    }

    /**
     * Sets the index of the sensitivity computed.
     *
     * @param parameterIndex The index.
     */
    public void setParameterIndex(final int parameterIndex) {
      this._parameterIndex = parameterIndex;
    }

    @SuppressWarnings("synthetic-access")
    @Override
    public Double evaluate(final Double x) {
      final double[] kD = super.kpkpp(x);
      // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative
      // of k.
      final EuropeanVanillaOption option =
          new EuropeanVanillaOption(x, super._timeToExpiry, super._isCall);
      final double[] priceDerivativeSABR = new double[4];
      getSabrExtrapolation().priceAdjointSABR(option, priceDerivativeSABR);
      return super._factor
          * (kD[1] * (x - super._strike) + 2.0 * kD[0])
          * priceDerivativeSABR[_parameterIndex];
    }
  }

  private class CMSStrikeIntegrant extends CMSIntegrant {

    /**
     * @param cmsCap
     * @param sabrParameter
     * @param forward
     */
    public CMSStrikeIntegrant(
        final CapFloorCMS cmsCap,
        final SABRFormulaData sabrPoint,
        final double forward,
        final double cutOffStrike,
        final double mu) {
      super(cmsCap, sabrPoint, forward, cutOffStrike, mu);
    }

    @Override
    public Double evaluate(final Double x) {
      final double[] kD = super.kpkpp(x);
      // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative
      // of k.
      return -kD[1] * bs(x);
    }
  } // End CMSStrikeIntegrant
}
/**
 * Test related to the pricing and sensitivity of the Ibor cap/floor with the SABR model and
 * extrapolation for high strikes.
 *
 * @deprecated This class tests deprecated functionality.
 */
@Deprecated
public class CapFloorIborSABRExtrapolationRightMethodTest {
  // Details
  private static final Period TENOR = Period.ofMonths(3);
  private static final int SETTLEMENT_DAYS = 2;
  private static final Calendar CALENDAR = new MondayToFridayCalendar("A");
  private static final DayCount DAY_COUNT_INDEX =
      DayCountFactory.INSTANCE.getDayCount("Actual/360");
  private static final BusinessDayConvention BUSINESS_DAY =
      BusinessDayConventionFactory.INSTANCE.getBusinessDayConvention("Modified Following");
  private static final boolean IS_EOM = true;
  private static final Currency CUR = Currency.EUR;
  private static final IborIndex INDEX =
      new IborIndex(CUR, TENOR, SETTLEMENT_DAYS, DAY_COUNT_INDEX, BUSINESS_DAY, IS_EOM, "Ibor");
  private static final ZonedDateTime FIXING_DATE = DateUtils.getUTCDate(2011, 1, 3);
  private static final double NOTIONAL = 1000000; // 1m
  private static final double STRIKE = 0.04;
  private static final double STRIKE_HIGH = 0.09;
  private static final boolean IS_CAP = true;
  // Definition description
  private static final CapFloorIborDefinition CAP_LONG_DEFINITION =
      CapFloorIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, STRIKE, IS_CAP, CALENDAR);
  private static final CapFloorIborDefinition CAP_HIGH_LONG_DEFINITION =
      CapFloorIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, IS_CAP, CALENDAR);
  private static final CouponIborDefinition COUPON_IBOR_DEFINITION =
      CouponIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, CALENDAR);
  private static final CouponFixedDefinition COUPON_STRIKE_DEFINITION =
      new CouponFixedDefinition(COUPON_IBOR_DEFINITION, STRIKE);
  private static final CouponFixedDefinition COUPON_STRIKE_HIGH_DEFINITION =
      new CouponFixedDefinition(COUPON_IBOR_DEFINITION, STRIKE_HIGH);
  private static final CapFloorIborDefinition CAP_SHORT_DEFINITION =
      CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE, IS_CAP, CALENDAR);
  private static final CapFloorIborDefinition CAP_HIGH_SHORT_DEFINITION =
      CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, IS_CAP, CALENDAR);
  private static final CapFloorIborDefinition FLOOR_SHORT_DEFINITION =
      CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE, !IS_CAP, CALENDAR);
  private static final CapFloorIborDefinition FLOOR_HIGH_SHORT_DEFINITION =
      CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, !IS_CAP, CALENDAR);
  // Methods and calculator
  private static final double CUT_OFF_STRIKE = 0.08;
  private static final double MU = 2.50;
  private static final CapFloorIborSABRExtrapolationRightMethod METHOD =
      new CapFloorIborSABRExtrapolationRightMethod(CUT_OFF_STRIKE, MU);
  private static final ParRateCalculator PRC = ParRateCalculator.getInstance();
  private static final PresentValueCalculator PVC = PresentValueCalculator.getInstance();
  private static final PresentValueCurveSensitivitySABRExtrapolationCalculator PVSC =
      new PresentValueCurveSensitivitySABRExtrapolationCalculator(CUT_OFF_STRIKE, MU);
  // To derivative
  private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2008, 8, 18);
  private static final String FUNDING_CURVE_NAME = "Funding";
  private static final String FORWARD_CURVE_NAME = "Forward";
  private static final String[] CURVES_NAME = {FUNDING_CURVE_NAME, FORWARD_CURVE_NAME};
  private static final CapFloorIbor CAP_LONG =
      (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CapFloorIbor CAP_HIGH_LONG =
      (CapFloorIbor) CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CouponIbor COUPON_IBOR =
      (CouponIbor) COUPON_IBOR_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CouponFixed COUPON_STRIKE =
      COUPON_STRIKE_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CouponFixed COUPON_STRIKE_HIGH =
      COUPON_STRIKE_HIGH_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CapFloorIbor CAP_SHORT =
      (CapFloorIbor) CAP_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CapFloorIbor CAP_HIGH_SHORT =
      (CapFloorIbor) CAP_HIGH_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CapFloorIbor FLOOR_SHORT =
      (CapFloorIbor) FLOOR_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  private static final CapFloorIbor FLOOR_HIGH_SHORT =
      (CapFloorIbor) FLOOR_HIGH_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME);
  // Data
  private static final YieldCurveBundle CURVES = TestsDataSetsSABR.createCurves1();
  private static final SABRInterestRateParameters SABR_PARAMETERS = TestsDataSetsSABR.createSABR1();
  private static final SABRInterestRateDataBundle SABR_BUNDLE =
      new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES);

  @Test
  /** Test the present value using the method with the direct formula with extrapolation. */
  public void presentValueBelowCutOff() {
    final CurrencyAmount methodPrice = METHOD.presentValue(CAP_LONG, SABR_BUNDLE);
    final double df =
        CURVES.getCurve(FUNDING_CURVE_NAME).getDiscountFactor(CAP_LONG.getPaymentTime());
    final double forward = CAP_LONG.accept(PRC, CURVES);
    final double maturity = CAP_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime();
    final DoublesPair expiryMaturity = new DoublesPair(CAP_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_LONG.getFixingTime(), MU);
    final EuropeanVanillaOption option =
        new EuropeanVanillaOption(CAP_LONG.getStrike(), CAP_LONG.getFixingTime(), CAP_LONG.isCap());
    final double expectedPrice =
        sabrExtrapolation.price(option)
            * CAP_LONG.getNotional()
            * CAP_LONG.getPaymentYearFraction()
            * df;
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2);
  }

  @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);
  }

  @Test
  /** Test the present value using the method with the direct formula with extrapolation. */
  public void presentValueLongShortParityBelowCutOff() {
    final CurrencyAmount priceLong = METHOD.presentValue(CAP_LONG, SABR_BUNDLE);
    final CurrencyAmount priceShort = METHOD.presentValue(CAP_SHORT, SABR_BUNDLE);
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing: long/short parity",
        priceLong.getAmount(),
        -priceShort.getAmount(),
        1E-2);
  }

  @Test
  /** Test the present value using the method with the direct formula with extrapolation. */
  public void presentValueLongShortParityAboveCutOff() {
    final CurrencyAmount priceLong = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE);
    final CurrencyAmount priceShort = METHOD.presentValue(CAP_HIGH_SHORT, SABR_BUNDLE);
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing: long/short parity",
        priceLong.getAmount(),
        -priceShort.getAmount(),
        1E-2);
  }

  @Test
  /** Test the cap/floor/forward parity below the cut-off strike. */
  public void presentValueCapFloorParityBelowCutOff() {
    final CurrencyAmount priceCap = METHOD.presentValue(CAP_LONG, SABR_BUNDLE);
    final CurrencyAmount priceFloor = METHOD.presentValue(FLOOR_SHORT, SABR_BUNDLE);
    final double priceCouponStrike = COUPON_STRIKE.accept(PVC, CURVES);
    final double priceCouponIbor = COUPON_IBOR.accept(PVC, CURVES);
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing: cap/floor parity",
        priceCouponIbor - priceCouponStrike,
        priceCap.getAmount() + priceFloor.getAmount(),
        1E-2);
  }

  @Test
  /** Test the cap/floor/forward parity above the cut-off strike. */
  public void presentValueCapFloorParityAboveCutOff() {
    final CurrencyAmount priceCap = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE);
    final CurrencyAmount priceFloor = METHOD.presentValue(FLOOR_HIGH_SHORT, SABR_BUNDLE);
    final double priceCouponStrike = COUPON_STRIKE_HIGH.accept(PVC, CURVES);
    final double priceCouponIbor = COUPON_IBOR.accept(PVC, CURVES);
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing: cap/floor parity",
        priceCouponIbor - priceCouponStrike,
        priceCap.getAmount() + priceFloor.getAmount(),
        1E-2);
  }

  @Test
  /** Test the present value using the method with the direct formula with extrapolation. */
  public void presentValueMethodVsCalculator() {
    final SABRInterestRateDataBundle sabrExtraBundle =
        new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES);
    final CurrencyAmount pvMethod = METHOD.presentValue(CAP_LONG, SABR_BUNDLE);
    final PresentValueSABRExtrapolationCalculator pvc =
        new PresentValueSABRExtrapolationCalculator(CUT_OFF_STRIKE, MU);
    final double pvCalculator = CAP_LONG.accept(pvc, sabrExtraBundle);
    assertEquals(
        "Cap/floor: SABR with extrapolation pricing - Method vs Calculator",
        pvMethod.getAmount(),
        pvCalculator,
        1E-2);
  }

  @Test
  /**
   * Test the present value rate sensitivity against a finite difference computation; strike below
   * the cut-off strike. Test sensitivity long/short parity.
   */
  public void testPresentValueSensitivityBelowCutOff() {
    final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1();
    final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1();
    final SABRInterestRateDataBundle sabrBundle =
        new SABRInterestRateDataBundle(sabrParameter, curves);
    InterestRateCurveSensitivity pvsCapLong = METHOD.presentValueSensitivity(CAP_LONG, sabrBundle);
    final InterestRateCurveSensitivity pvsCapShort =
        METHOD.presentValueSensitivity(CAP_SHORT, sabrBundle);
    // Long/short parity
    final InterestRateCurveSensitivity pvsCapShort_1 = pvsCapShort.multipliedBy(-1);
    assertEquals(pvsCapLong.getSensitivities(), pvsCapShort_1.getSensitivities());
    // Present value sensitivity comparison with finite difference.
    final double deltaTolerancePrice = 1.0E-1;
    // Testing note: Sensitivity is for a movement of 1. 1E+2 = 1 cent for a 1 bp move.
    final double deltaShift = 1.0E-7;
    pvsCapLong = pvsCapLong.cleaned();
    final String bumpedCurveName = "Bumped Curve";
    // 1. Forward curve sensitivity
    final String[] CurveNameBumpedForward = {FUNDING_CURVE_NAME, bumpedCurveName};
    final CapFloorIbor capBumpedForward =
        (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedForward);
    final double[] nodeTimesForward =
        new double[] {
          capBumpedForward.getFixingPeriodStartTime(), capBumpedForward.getFixingPeriodEndTime()
        };
    final double[] sensiForwardMethod =
        SensitivityFiniteDifference.curveSensitivity(
            capBumpedForward,
            SABR_BUNDLE,
            FORWARD_CURVE_NAME,
            bumpedCurveName,
            nodeTimesForward,
            deltaShift,
            METHOD);
    assertEquals(
        "Sensitivity finite difference method: number of node", 2, sensiForwardMethod.length);
    final List<DoublesPair> sensiPvForward = pvsCapLong.getSensitivities().get(FORWARD_CURVE_NAME);
    for (int loopnode = 0; loopnode < sensiForwardMethod.length; loopnode++) {
      final DoublesPair pairPv = sensiPvForward.get(loopnode);
      assertEquals(
          "Sensitivity cap/floor pv to forward curve: Node " + loopnode,
          nodeTimesForward[loopnode],
          pairPv.getFirst(),
          1E-8);
      assertEquals(
          "Sensitivity finite difference method: node sensitivity",
          pairPv.second,
          sensiForwardMethod[loopnode],
          deltaTolerancePrice);
    }
    // 2. Discounting curve sensitivity
    final String[] CurveNameBumpedDisc = {bumpedCurveName, FORWARD_CURVE_NAME};
    final CapFloorIbor capBumpedDisc =
        (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedDisc);
    final double[] nodeTimesDisc = new double[] {capBumpedDisc.getPaymentTime()};
    final double[] sensiDiscMethod =
        SensitivityFiniteDifference.curveSensitivity(
            capBumpedDisc,
            SABR_BUNDLE,
            FUNDING_CURVE_NAME,
            bumpedCurveName,
            nodeTimesDisc,
            deltaShift,
            METHOD);
    assertEquals("Sensitivity finite difference method: number of node", 1, sensiDiscMethod.length);
    final List<DoublesPair> sensiPvDisc = pvsCapLong.getSensitivities().get(FUNDING_CURVE_NAME);
    for (int loopnode = 0; loopnode < sensiDiscMethod.length; loopnode++) {
      final DoublesPair pairPv = sensiPvDisc.get(loopnode);
      assertEquals(
          "Sensitivity cap/floor pv to forward curve: Node " + loopnode,
          nodeTimesDisc[loopnode],
          pairPv.getFirst(),
          1E-8);
      assertEquals(
          "Sensitivity finite difference method: node sensitivity",
          pairPv.second,
          sensiDiscMethod[loopnode],
          deltaTolerancePrice);
    }
  }

  @Test
  /**
   * Test the present value rate sensitivity against a finite difference computation; strike above
   * the cut-off strike. Test sensitivity long/short parity.
   */
  public void testPresentValueSensitivityAboveCutOff() {
    final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1();
    final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1();
    final SABRInterestRateDataBundle sabrBundle =
        new SABRInterestRateDataBundle(sabrParameter, curves);
    InterestRateCurveSensitivity pvsCapLong =
        METHOD.presentValueSensitivity(CAP_HIGH_LONG, sabrBundle);
    final InterestRateCurveSensitivity pvsCapShort =
        METHOD.presentValueSensitivity(CAP_HIGH_SHORT, sabrBundle);
    // Long/short parity
    final InterestRateCurveSensitivity pvsCapShort_1 = pvsCapShort.multipliedBy(-1);
    assertEquals(pvsCapLong.getSensitivities(), pvsCapShort_1.getSensitivities());
    // Present value sensitivity comparison with finite difference.
    final double deltaTolerancePrice = 1.0E-1;
    // Testing note: Sensitivity is for a movement of 1. 1E+2 = 1 cent for a 1 bp move.
    final double deltaShift = 1.0E-7;
    pvsCapLong = pvsCapLong.cleaned();
    final String bumpedCurveName = "Bumped Curve";
    // 1. Forward curve sensitivity
    final String[] CurveNameBumpedForward = {FUNDING_CURVE_NAME, bumpedCurveName};
    final CapFloorIbor capBumpedForward =
        (CapFloorIbor)
            CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedForward);
    final double[] nodeTimesForward =
        new double[] {
          capBumpedForward.getFixingPeriodStartTime(), capBumpedForward.getFixingPeriodEndTime()
        };
    final double[] sensiForwardMethod =
        SensitivityFiniteDifference.curveSensitivity(
            capBumpedForward,
            SABR_BUNDLE,
            FORWARD_CURVE_NAME,
            bumpedCurveName,
            nodeTimesForward,
            deltaShift,
            METHOD);
    assertEquals(
        "Sensitivity finite difference method: number of node", 2, sensiForwardMethod.length);
    final List<DoublesPair> sensiPvForward = pvsCapLong.getSensitivities().get(FORWARD_CURVE_NAME);
    for (int loopnode = 0; loopnode < sensiForwardMethod.length; loopnode++) {
      final DoublesPair pairPv = sensiPvForward.get(loopnode);
      assertEquals(
          "Sensitivity cap/floor pv to forward curve: Node " + loopnode,
          nodeTimesForward[loopnode],
          pairPv.getFirst(),
          1E-8);
      //      assertEquals("Sensitivity finite difference method: node sensitivity: Node " +
      // loopnode, pairPv.second, sensiForwardMethod[loopnode], deltaTolerancePrice);
    }
    // 2. Discounting curve sensitivity
    final String[] CurveNameBumpedDisc = {bumpedCurveName, FORWARD_CURVE_NAME};
    final CapFloorIbor capBumpedDisc =
        (CapFloorIbor) CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedDisc);
    final double[] nodeTimesDisc = new double[] {capBumpedDisc.getPaymentTime()};
    final double[] sensiDiscMethod =
        SensitivityFiniteDifference.curveSensitivity(
            capBumpedDisc,
            SABR_BUNDLE,
            FUNDING_CURVE_NAME,
            bumpedCurveName,
            nodeTimesDisc,
            deltaShift,
            METHOD);
    assertEquals("Sensitivity finite difference method: number of node", 1, sensiDiscMethod.length);
    final List<DoublesPair> sensiPvDisc = pvsCapLong.getSensitivities().get(FUNDING_CURVE_NAME);
    for (int loopnode = 0; loopnode < sensiDiscMethod.length; loopnode++) {
      final DoublesPair pairPv = sensiPvDisc.get(loopnode);
      assertEquals(
          "Sensitivity cap/floor pv to forward curve: Node " + loopnode,
          nodeTimesDisc[loopnode],
          pairPv.getFirst(),
          1E-8);
      assertEquals(
          "Sensitivity finite difference method: node sensitivity",
          pairPv.second,
          sensiDiscMethod[loopnode],
          deltaTolerancePrice);
    }
  }

  @Test
  /** Test the present value using the method with the direct formula with extrapolation. */
  public void presentValueCurveSensitivityMethodVsCalculator() {
    final SABRInterestRateDataBundle sabrExtraBundle =
        new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES);
    final InterestRateCurveSensitivity pvsMethod =
        METHOD.presentValueSensitivity(CAP_HIGH_LONG, SABR_BUNDLE);
    final InterestRateCurveSensitivity pvsCalculator =
        new InterestRateCurveSensitivity(CAP_HIGH_LONG.accept(PVSC, sabrExtraBundle));
    assertEquals(
        "Cap/floor: SABR with extrapolation pv curve sensitivity - Method vs Calculator",
        pvsMethod,
        pvsCalculator);
  }

  @Test
  /**
   * Test the present value SABR parameters sensitivity against a finite difference computation;
   * strike below the cut-off strike.
   */
  public void testPresentValueSABRSensitivityBelowCutOff() {
    final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1();
    final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1();
    final SABRInterestRateDataBundle sabrBundle =
        new SABRInterestRateDataBundle(sabrParameter, curves);
    final CurrencyAmount pv = METHOD.presentValue(CAP_LONG, sabrBundle);
    final PresentValueSABRSensitivityDataBundle pvsCapLong =
        METHOD.presentValueSABRSensitivity(CAP_LONG, sabrBundle);
    PresentValueSABRSensitivityDataBundle pvsCapShort =
        METHOD.presentValueSABRSensitivity(CAP_SHORT, sabrBundle);
    // Long/short parity
    pvsCapShort = pvsCapShort.multiplyBy(-1.0);
    assertEquals(pvsCapShort.getAlpha(), pvsCapLong.getAlpha());
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final DoublesPair expectedExpiryTenor =
        new DoublesPair(
            CAP_LONG.getFixingTime(),
            CAP_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime());
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        TestsDataSetsSABR.createSABR1AlphaBumped(shiftAlpha);
    final SABRInterestRateDataBundle sabrBundleAlphaBumped =
        new SABRInterestRateDataBundle(sabrParameterAlphaBumped, curves);
    final CurrencyAmount pvLongPayerAlphaBumped =
        METHOD.presentValue(CAP_LONG, sabrBundleAlphaBumped);
    final double expectedAlphaSensi =
        (pvLongPayerAlphaBumped.getAmount() - pv.getAmount()) / 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),
        2.0E-1);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped =
        TestsDataSetsSABR.createSABR1RhoBumped();
    final SABRInterestRateDataBundle sabrBundleRhoBumped =
        new SABRInterestRateDataBundle(sabrParameterRhoBumped, curves);
    final CurrencyAmount pvLongPayerRhoBumped = METHOD.presentValue(CAP_LONG, sabrBundleRhoBumped);
    final double expectedRhoSensi = (pvLongPayerRhoBumped.getAmount() - pv.getAmount()) / 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",
        pvsCapLong.getRho().getMap().get(expectedExpiryTenor),
        expectedRhoSensi,
        1.0E-2);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped =
        TestsDataSetsSABR.createSABR1NuBumped();
    final SABRInterestRateDataBundle sabrBundleNuBumped =
        new SABRInterestRateDataBundle(sabrParameterNuBumped, curves);
    final CurrencyAmount pvLongPayerNuBumped = METHOD.presentValue(CAP_LONG, sabrBundleNuBumped);
    final double expectedNuSensi = (pvLongPayerNuBumped.getAmount() - pv.getAmount()) / shift;
    assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1);
    assertEquals(
        "Nu sensitivity expiry/tenor",
        pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Nu sensitivity value",
        pvsCapLong.getNu().getMap().get(expectedExpiryTenor),
        expectedNuSensi,
        3.0E-2);
  }

  @Test
  /**
   * Test the present value SABR parameters sensitivity against a finite difference computation;
   * strike above the cut-off strike.
   */
  public void testPresentValueSABRSensitivityAboveCutOff() {
    final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1();
    final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1();
    final SABRInterestRateDataBundle sabrBundle =
        new SABRInterestRateDataBundle(sabrParameter, curves);
    final CurrencyAmount pv = METHOD.presentValue(CAP_HIGH_LONG, sabrBundle);
    final PresentValueSABRSensitivityDataBundle pvsCapLong =
        METHOD.presentValueSABRSensitivity(CAP_HIGH_LONG, sabrBundle);
    PresentValueSABRSensitivityDataBundle pvsCapShort =
        METHOD.presentValueSABRSensitivity(CAP_HIGH_SHORT, sabrBundle);
    // Long/short parity
    pvsCapShort = pvsCapShort.multiplyBy(-1.0);
    assertEquals(pvsCapShort.getAlpha(), pvsCapLong.getAlpha());
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final DoublesPair expectedExpiryTenor =
        new DoublesPair(
            CAP_HIGH_LONG.getFixingTime(),
            CAP_HIGH_LONG.getFixingPeriodEndTime() - CAP_HIGH_LONG.getFixingPeriodStartTime());
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        TestsDataSetsSABR.createSABR1AlphaBumped(shiftAlpha);
    final SABRInterestRateDataBundle sabrBundleAlphaBumped =
        new SABRInterestRateDataBundle(sabrParameterAlphaBumped, curves);
    final CurrencyAmount pvLongPayerAlphaBumped =
        METHOD.presentValue(CAP_HIGH_LONG, sabrBundleAlphaBumped);
    final double expectedAlphaSensi =
        (pvLongPayerAlphaBumped.getAmount() - pv.getAmount()) / 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),
        1.0E-0);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped =
        TestsDataSetsSABR.createSABR1RhoBumped();
    final SABRInterestRateDataBundle sabrBundleRhoBumped =
        new SABRInterestRateDataBundle(sabrParameterRhoBumped, curves);
    final CurrencyAmount pvLongPayerRhoBumped =
        METHOD.presentValue(CAP_HIGH_LONG, sabrBundleRhoBumped);
    final double expectedRhoSensi = (pvLongPayerRhoBumped.getAmount() - pv.getAmount()) / 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",
        pvsCapLong.getRho().getMap().get(expectedExpiryTenor),
        expectedRhoSensi,
        1.0E-1);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped =
        TestsDataSetsSABR.createSABR1NuBumped();
    final SABRInterestRateDataBundle sabrBundleNuBumped =
        new SABRInterestRateDataBundle(sabrParameterNuBumped, curves);
    final CurrencyAmount pvLongPayerNuBumped =
        METHOD.presentValue(CAP_HIGH_LONG, sabrBundleNuBumped);
    final double expectedNuSensi = (pvLongPayerNuBumped.getAmount() - pv.getAmount()) / shift;
    assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1);
    assertEquals(
        "Nu sensitivity expiry/tenor",
        pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Nu sensitivity value",
        pvsCapLong.getNu().getMap().get(expectedExpiryTenor),
        expectedNuSensi,
        2.0E-1);
  }
}
/**
 * Constructs a single yield curve and its Jacobian from an FX-implied yield curve calculation
 * configuration and a yield curve definition that contains <b>only</b> {@link
 * StripInstrumentType#CASH} strips. The transformation of the yield curve allows risk to be
 * displayed with respect to implied deposit rates, not FX forwards.
 */
public class ImpliedDepositCurveFunction extends AbstractFunction {
  /** The calculation method property value */
  public static final String IMPLIED_DEPOSIT = "ImpliedDeposit";
  /** The Cash instrument method */
  private static final CashDiscountingMethod METHOD_CASH = CashDiscountingMethod.getInstance();
  /** Calculates the par rate */
  private static final ParRateCalculator PAR_RATE_CALCULATOR = ParRateCalculator.getInstance();
  /** Calculates the sensitivity of the par rate to the curves */
  private static final ParRateCurveSensitivityCalculator PAR_RATE_SENSITIVITY_CALCULATOR =
      ParRateCurveSensitivityCalculator.getInstance();
  /** The business day convention used for FX forward dates computation * */
  private static final BusinessDayConvention MOD_FOL = BusinessDayConventions.MODIFIED_FOLLOWING;
  /** The logger */
  private static final Logger s_logger = LoggerFactory.getLogger(ImpliedDepositCurveFunction.class);
  /** The curve name */
  private final String _curveCalculationConfig;

  private ConfigSourceQuery<MultiCurveCalculationConfig> _multiCurveCalculationConfig;
  private ConfigSourceQuery<YieldCurveDefinition> _yieldCurveDefinition;

  /** @param curveCalculationConfig The curve name, not null */
  public ImpliedDepositCurveFunction(final String curveCalculationConfig) {
    ArgumentChecker.notNull(curveCalculationConfig, "curve name");
    _curveCalculationConfig = curveCalculationConfig;
  }

  @Override
  public void init(final FunctionCompilationContext context) {
    _multiCurveCalculationConfig =
        ConfigSourceQuery.init(context, this, MultiCurveCalculationConfig.class);
    _yieldCurveDefinition = ConfigSourceQuery.init(context, this, YieldCurveDefinition.class);
  }

  @Override
  public CompiledFunctionDefinition compile(
      final FunctionCompilationContext context, final Instant atInstant) {
    final MultiCurveCalculationConfig impliedConfiguration =
        _multiCurveCalculationConfig.get(_curveCalculationConfig);
    if (impliedConfiguration == null) {
      throw new OpenGammaRuntimeException(
          "Multi-curve calculation called " + _curveCalculationConfig + " was null");
    }
    ComputationTarget target =
        context.getComputationTargetResolver().resolve(impliedConfiguration.getTarget());
    if (!(target.getValue() instanceof Currency)) {
      throw new OpenGammaRuntimeException(
          "Target of curve calculation configuration was not a currency");
    }
    final Currency impliedCurrency = (Currency) target.getValue();
    if (!IMPLIED_DEPOSIT.equals(impliedConfiguration.getCalculationMethod())) {
      throw new OpenGammaRuntimeException(
          "Curve calculation method was not "
              + IMPLIED_DEPOSIT
              + " for configuration called "
              + _curveCalculationConfig);
    }
    final String[] impliedCurveNames = impliedConfiguration.getYieldCurveNames();
    if (impliedCurveNames.length != 1) {
      throw new OpenGammaRuntimeException(
          "Can only handle configurations with a single implied curve");
    }
    final LinkedHashMap<String, String[]> originalConfigurationName =
        impliedConfiguration.getExogenousConfigData();
    if (originalConfigurationName == null || originalConfigurationName.size() != 1) {
      throw new OpenGammaRuntimeException("Need a configuration with one exogenous configuration");
    }
    final Map.Entry<String, String[]> entry =
        Iterables.getOnlyElement(originalConfigurationName.entrySet());
    final String[] originalCurveNames = entry.getValue();
    if (originalCurveNames.length != 1) {
      s_logger.warn("Found more than one exogenous configuration name; using only the first");
    }
    final MultiCurveCalculationConfig originalConfiguration =
        _multiCurveCalculationConfig.get(entry.getKey());
    if (originalConfiguration == null) {
      throw new OpenGammaRuntimeException(
          "Multi-curve calculation called " + entry.getKey() + " was null");
    }
    target = context.getComputationTargetResolver().resolve(originalConfiguration.getTarget());
    if (!(target.getValue() instanceof Currency)) {
      throw new OpenGammaRuntimeException(
          "Target of curve calculation configuration was not a currency");
    }
    final Currency originalCurrency = (Currency) target.getValue();
    if (!originalCurrency.equals(impliedCurrency)) {
      throw new OpenGammaRuntimeException(
          "Currency targets for configurations "
              + _curveCalculationConfig
              + " and "
              + entry.getKey()
              + " did not match");
    }
    final YieldCurveDefinition impliedDefinition =
        _yieldCurveDefinition.get(impliedCurveNames[0] + "_" + impliedCurrency.getCode());
    if (impliedDefinition == null) {
      throw new OpenGammaRuntimeException(
          "Could not get implied definition called "
              + impliedCurveNames[0]
              + "_"
              + impliedCurrency.getCode());
    }
    final Set<FixedIncomeStrip> strips = impliedDefinition.getStrips();
    for (final FixedIncomeStrip strip : strips) {
      if (strip.getInstrumentType() != StripInstrumentType.CASH) {
        throw new OpenGammaRuntimeException(
            "Can only handle yield curve definitions with CASH strips");
      }
    }
    final ZonedDateTime atZDT = ZonedDateTime.ofInstant(atInstant, ZoneOffset.UTC);
    return new MyCompiledFunction(
        atZDT.with(LocalTime.MIDNIGHT),
        atZDT.plusDays(1).with(LocalTime.MIDNIGHT).minusNanos(1000000),
        impliedConfiguration,
        impliedDefinition,
        originalConfiguration,
        originalCurveNames[0]);
  };

  private class MyCompiledFunction extends AbstractInvokingCompiledFunction {
    /** The definition of the implied curve */
    private final YieldCurveDefinition _impliedDefinition;
    /** The implied curve calculation configuration */
    private final MultiCurveCalculationConfig _impliedConfiguration;
    /** The original curve calculation configuration */
    private final MultiCurveCalculationConfig _originalConfiguration;
    /** The implied curve name */
    private final String _impliedCurveName;
    /** The original curve name */
    private final String _originalCurveName;
    /** The currency */
    private final Currency _currency;
    /** The interpolator */
    private final String _interpolatorName;
    /** The left extrapolator */
    private final String _leftExtrapolatorName;
    /** The right extrapolator */
    private final String _rightExtrapolatorName;

    public MyCompiledFunction(
        final ZonedDateTime earliestInvokation,
        final ZonedDateTime latestInvokation,
        final MultiCurveCalculationConfig impliedConfiguration,
        final YieldCurveDefinition impliedDefinition,
        final MultiCurveCalculationConfig originalConfiguration,
        final String originalCurveName) {
      super(earliestInvokation, latestInvokation);
      _impliedConfiguration = impliedConfiguration;
      _impliedDefinition = impliedDefinition;
      _originalConfiguration = originalConfiguration;
      _impliedCurveName = impliedDefinition.getName();
      _originalCurveName = originalCurveName;
      _currency = impliedDefinition.getCurrency();
      _interpolatorName = impliedDefinition.getInterpolatorName();
      _leftExtrapolatorName = impliedDefinition.getLeftExtrapolatorName();
      _rightExtrapolatorName = impliedDefinition.getRightExtrapolatorName();
    }

    @Override
    public Set<ComputedValue> execute(
        final FunctionExecutionContext executionContext,
        final FunctionInputs inputs,
        final ComputationTarget target,
        final Set<ValueRequirement> desiredValues)
        throws AsynchronousExecution {
      final Object originalCurveObject = inputs.getValue(YIELD_CURVE);
      if (originalCurveObject == null) {
        throw new OpenGammaRuntimeException("Could not get original curve");
      }
      ValueProperties resultCurveProperties = null;
      String absoluteToleranceName = null;
      String relativeToleranceName = null;
      String iterationsName = null;
      String decompositionName = null;
      String useFiniteDifferenceName = null;
      for (final ValueRequirement desiredValue : desiredValues) {
        if (desiredValue.getValueName().equals(YIELD_CURVE)) {
          absoluteToleranceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE);
          relativeToleranceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE);
          iterationsName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_MAX_ITERATIONS);
          decompositionName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_DECOMPOSITION);
          useFiniteDifferenceName =
              desiredValue.getConstraint(
                  MultiYieldCurvePropertiesAndDefaults.PROPERTY_USE_FINITE_DIFFERENCE);
          resultCurveProperties = desiredValue.getConstraints().copy().get();
          break;
        }
      }
      if (resultCurveProperties == null) {
        throw new OpenGammaRuntimeException("Could not get result curve properties");
      }
      final ValueProperties resultJacobianProperties = resultCurveProperties.withoutAny(CURVE);
      ZonedDateTime valuationDateTime =
          executionContext
              .getValuationTime()
              .atZone(executionContext.getValuationClock().getZone());
      final HolidaySource holidaySource =
          OpenGammaExecutionContext.getHolidaySource(executionContext);
      final ConventionSource conventionSource =
          OpenGammaExecutionContext.getConventionSource(executionContext);
      final Calendar calendar = CalendarUtils.getCalendar(holidaySource, _currency);
      final DepositConvention convention =
          conventionSource.getSingle(
              ExternalId.of(SCHEME_NAME, getConventionName(_currency, DEPOSIT)),
              DepositConvention.class);
      final int spotLag = convention.getSettlementDays();
      final ExternalId conventionSettlementRegion = convention.getRegionCalendar();
      ZonedDateTime spotDate;
      if (spotLag == 0 && conventionSettlementRegion == null) {
        spotDate = valuationDateTime;
      } else {
        spotDate = ScheduleCalculator.getAdjustedDate(valuationDateTime, spotLag, calendar);
        ;
      }
      final YieldCurveBundle curves = new YieldCurveBundle();
      final String fullYieldCurveName = _originalCurveName + "_" + _currency;
      curves.setCurve(fullYieldCurveName, (YieldAndDiscountCurve) originalCurveObject);
      final int n = _impliedDefinition.getStrips().size();
      final double[] t = new double[n];
      final double[] r = new double[n];
      int i = 0;
      final DayCount dayCount =
          DayCountFactory.INSTANCE.getDayCount(
              "Act/360"); // TODO: Get the convention from the curve.

      final String impliedDepositCurveName = _curveCalculationConfig + "_" + _currency.getCode();
      final List<InstrumentDerivative> derivatives = new ArrayList<>();

      for (final FixedIncomeStrip strip : _impliedDefinition.getStrips()) {
        final Tenor tenor = strip.getCurveNodePointTime();
        final ZonedDateTime paymentDate =
            ScheduleCalculator.getAdjustedDate(
                spotDate, tenor.getPeriod(), MOD_FOL, calendar, true);
        final double startTime = TimeCalculator.getTimeBetween(valuationDateTime, spotDate);
        final double endTime = TimeCalculator.getTimeBetween(valuationDateTime, paymentDate);
        final double accrualFactor = dayCount.getDayCountFraction(spotDate, paymentDate, calendar);
        final Cash cashFXCurve =
            new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, fullYieldCurveName);
        final double parRate = METHOD_CASH.parRate(cashFXCurve, curves);
        final Cash cashDepositCurve =
            new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, impliedDepositCurveName);
        derivatives.add(cashDepositCurve);
        t[i] = endTime;
        r[i++] = parRate;
      }
      final CombinedInterpolatorExtrapolator interpolator =
          CombinedInterpolatorExtrapolatorFactory.getInterpolator(
              _interpolatorName, _leftExtrapolatorName, _rightExtrapolatorName);
      final double absoluteTolerance = Double.parseDouble(absoluteToleranceName);
      final double relativeTolerance = Double.parseDouble(relativeToleranceName);
      final int iterations = Integer.parseInt(iterationsName);
      final Decomposition<?> decomposition =
          DecompositionFactory.getDecomposition(decompositionName);
      final boolean useFiniteDifference = Boolean.parseBoolean(useFiniteDifferenceName);
      final LinkedHashMap<String, double[]> curveNodes = new LinkedHashMap<>();
      final LinkedHashMap<String, Interpolator1D> interpolators = new LinkedHashMap<>();
      curveNodes.put(impliedDepositCurveName, t);
      interpolators.put(impliedDepositCurveName, interpolator);
      final FXMatrix fxMatrix = new FXMatrix();
      final YieldCurveBundle knownCurve = new YieldCurveBundle();
      final MultipleYieldCurveFinderDataBundle data =
          new MultipleYieldCurveFinderDataBundle(
              derivatives, r, knownCurve, curveNodes, interpolators, useFiniteDifference, fxMatrix);
      final NewtonVectorRootFinder rootFinder =
          new BroydenVectorRootFinder(
              absoluteTolerance, relativeTolerance, iterations, decomposition);
      final Function1D<DoubleMatrix1D, DoubleMatrix1D> curveCalculator =
          new MultipleYieldCurveFinderFunction(data, PAR_RATE_CALCULATOR);
      final Function1D<DoubleMatrix1D, DoubleMatrix2D> jacobianCalculator =
          new MultipleYieldCurveFinderJacobian(data, PAR_RATE_SENSITIVITY_CALCULATOR);
      final double[] fittedYields =
          rootFinder.getRoot(curveCalculator, jacobianCalculator, new DoubleMatrix1D(r)).getData();
      final DoubleMatrix2D jacobianMatrix =
          jacobianCalculator.evaluate(new DoubleMatrix1D(fittedYields));
      final YieldCurve impliedDepositCurve =
          new YieldCurve(
              impliedDepositCurveName,
              InterpolatedDoublesCurve.from(t, fittedYields, interpolator));
      final ValueSpecification curveSpec =
          new ValueSpecification(YIELD_CURVE, target.toSpecification(), resultCurveProperties);
      final ValueSpecification jacobianSpec =
          new ValueSpecification(
              YIELD_CURVE_JACOBIAN, target.toSpecification(), resultJacobianProperties);
      return Sets.newHashSet(
          new ComputedValue(curveSpec, impliedDepositCurve),
          new ComputedValue(jacobianSpec, jacobianMatrix));
    }

    @Override
    public ComputationTargetType getTargetType() {
      return ComputationTargetType.CURRENCY;
    }

    @Override
    public Set<ValueSpecification> getResults(
        final FunctionCompilationContext compilationContext, final ComputationTarget target) {
      final ValueProperties curveProperties =
          getCurveProperties(_impliedCurveName, _impliedConfiguration.getCalculationConfigName());
      final ValueProperties jacobianProperties =
          getJacobianProperties(_impliedConfiguration.getCalculationConfigName());
      final ComputationTargetSpecification targetSpec = target.toSpecification();
      final ValueSpecification curve =
          new ValueSpecification(YIELD_CURVE, targetSpec, curveProperties);
      final ValueSpecification jacobian =
          new ValueSpecification(YIELD_CURVE_JACOBIAN, targetSpec, jacobianProperties);
      return Sets.newHashSet(curve, jacobian);
    }

    @Override
    public Set<ValueRequirement> getRequirements(
        final FunctionCompilationContext compilationContext,
        final ComputationTarget target,
        final ValueRequirement desiredValue) {
      final ValueProperties constraints = desiredValue.getConstraints();
      final Set<String> rootFinderAbsoluteTolerance =
          constraints.getValues(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE);
      if (rootFinderAbsoluteTolerance == null || rootFinderAbsoluteTolerance.size() != 1) {
        return null;
      }
      final Set<String> rootFinderRelativeTolerance =
          constraints.getValues(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE);
      if (rootFinderRelativeTolerance == null || rootFinderRelativeTolerance.size() != 1) {
        return null;
      }
      final Set<String> maxIterations = constraints.getValues(PROPERTY_ROOT_FINDER_MAX_ITERATIONS);
      if (maxIterations == null || maxIterations.size() != 1) {
        return null;
      }
      final Set<String> decomposition = constraints.getValues(PROPERTY_DECOMPOSITION);
      if (decomposition == null || decomposition.size() != 1) {
        return null;
      }
      final Set<String> useFiniteDifference = constraints.getValues(PROPERTY_USE_FINITE_DIFFERENCE);
      if (useFiniteDifference == null || useFiniteDifference.size() != 1) {
        return null;
      }
      if (!_originalConfiguration.getTarget().equals(target.toSpecification())) {
        s_logger.info(
            "Invalid target, was {} - expected {}", target, _originalConfiguration.getTarget());
        return null;
      }
      final ValueProperties properties =
          ValueProperties.builder()
              .with(CURVE_CALCULATION_METHOD, _originalConfiguration.getCalculationMethod())
              .with(CURVE_CALCULATION_CONFIG, _originalConfiguration.getCalculationConfigName())
              .with(CURVE, _originalCurveName)
              .get();
      return Collections.singleton(
          new ValueRequirement(YIELD_CURVE, target.toSpecification(), properties));
    }

    /**
     * Gets the properties of the implied yield curve.
     *
     * @param curveName The implied curve name
     * @return The properties
     */
    private ValueProperties getCurveProperties(
        final String curveName, final String curveCalculationConfig) {
      return createValueProperties()
          .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT)
          .with(CURVE, curveName)
          .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig)
          .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS)
          .withAny(PROPERTY_DECOMPOSITION)
          .withAny(PROPERTY_USE_FINITE_DIFFERENCE)
          .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName)
          .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName)
          .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName)
          .get();
    }

    /**
     * Gets the properties of the Jacobian with no values set.
     *
     * @return The properties.
     */
    private ValueProperties getJacobianProperties(final String curveCalculationConfig) {
      return createValueProperties()
          .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT)
          .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig)
          .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE)
          .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS)
          .withAny(PROPERTY_DECOMPOSITION)
          .withAny(PROPERTY_USE_FINITE_DIFFERENCE)
          .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName)
          .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName)
          .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName)
          .get();
    }
  }
}