private double fdCentral(
      CdsAnalytic pricingCDS,
      double cdsSpread,
      CdsAnalytic[] curvePoints,
      double[] spreads,
      double[] deltaSpreads,
      IsdaCompliantYieldCurve yieldCurve,
      CdsPriceType priceType) {

    int n = curvePoints.length;
    double[] spreadUp = new double[n];
    double[] spreadDown = new double[n];
    for (int i = 0; i < n; i++) {
      spreadUp[i] = spreads[i] + deltaSpreads[i];
      spreadDown[i] = spreads[i] - deltaSpreads[i];
    }
    IsdaCompliantCreditCurve curveUp =
        _curveBuilder.calibrateCreditCurve(curvePoints, spreadUp, yieldCurve);
    IsdaCompliantCreditCurve curveDown =
        _curveBuilder.calibrateCreditCurve(curvePoints, spreadDown, yieldCurve);
    double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType);
    double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType);

    return up - down;
  }
  /**
   * The bucked CS01 (or credit DV01) by shifting each market par-spread in turn. This takes an
   * extraneous yield curve, a set of reference CDSs (marketCDSs) and their par-spreads (expressed
   * as <b>fractions not basis points</b>) and bootstraps a credit (hazard) curve - the target CDS
   * is then priced with this credit curve. This is then repeated with each market spreads bumped in
   * turn. The result is the vector of differences (bumped minus base price) divided by the bump
   * amount.<br>
   * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S_i}$$ where $$S_i$$ is
   * the spread of the $$1^{th}$$ market CDS<br>
   *
   * @param cds analytic description of a CDS traded at a certain time
   * @param cdsCoupon The <b>fraction</b> spread of the CDS
   * @param yieldCurve The yield (or discount) curve
   * @param marketCDSs The market CDSs - these are the reference instruments used to build the
   *     credit curve
   * @param marketParSpreads The <b>fractional</b> par-spreads of the market CDSs
   * @param fracBumpAmount The fraction bump amount, so a 1pb bump is 1e-4
   * @param shiftType ABSOLUTE or RELATIVE
   * @return The credit CS01
   */
  public double[] bucketedCS01FromParSpreads(
      CdsAnalytic cds,
      double cdsCoupon,
      IsdaCompliantYieldCurve yieldCurve,
      CdsAnalytic[] marketCDSs,
      double[] marketParSpreads,
      double fracBumpAmount,
      ShiftType shiftType) {

    ArgChecker.notNull(cds, "cds");
    ArgChecker.noNulls(marketCDSs, "curvePoints");
    ArgChecker.notEmpty(marketParSpreads, "spreads");
    ArgChecker.notNull(yieldCurve, "yieldCurve");
    ArgChecker.notNull(shiftType, "shiftType");
    ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
    int n = marketCDSs.length;
    ArgChecker.isTrue(n == marketParSpreads.length, "speads length does not match curvePoints");
    CdsPriceType priceType = CdsPriceType.DIRTY;

    IsdaCompliantCreditCurve baseCurve =
        _curveBuilder.calibrateCreditCurve(marketCDSs, marketParSpreads, yieldCurve);
    double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon, priceType);

    double[] res = new double[n];
    for (int i = 0; i < n; i++) {
      double[] temp = makeBumpedSpreads(marketParSpreads, fracBumpAmount, shiftType, i);
      IsdaCompliantCreditCurve bumpedCurve =
          _curveBuilder.calibrateCreditCurve(marketCDSs, temp, yieldCurve);
      double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon, priceType);
      res[i] = (price - basePrice) / fracBumpAmount;
    }

    return res;
  }
  /**
   * The bucked CS01 (or credit DV01) by a shift of each the market spread in turn. This takes an
   * extraneous yield curve, a set of reference CDSs (marketCDSs) and their market quotes and
   * bootstraps a credit (hazard) curve - the target CDS is then priced with this credit curve. This
   * is then repeated with each market spreads bumped in turn by some amount. The result is the
   * array of differences (bumped minus base price) is divided by the bump amount.<br>
   * This can take quotes as ParSpread, PointsUpFront or QuotedSpread (or some mix). For
   * par-spreads, these are bumped and a new credit curve built; for quoted-spreads, there are
   * bumped and a new curve build be first converting to PUF; and finally for PUF, these are
   * converted to quoted spreads, bumped and converted back to build the credit curve.
   *
   * @param cds analytic description of a CDS traded at a certain time - it is this CDS that we are
   *     calculation CDV01 for
   * @param cdsCoupon the coupon of the traded CDS (expressed as <b>fractions not basis points</b>)
   * @param yieldCurve The yield (or discount) curve
   * @param marketCDSs The market CDSs - these are the reference instruments used to build the
   *     credit curve
   * @param quotes The quotes for the market CDSs - these can be ParSpread, PointsUpFront or
   *     QuotedSpread (or any mixture of these)
   * @param fracBumpAmount The fraction bump amount, so a 1pb bump is 1e-4
   * @return The bucketed credit DV01
   */
  public double[] bucketedCS01FromPillarQuotes(
      CdsAnalytic cds,
      double cdsCoupon,
      IsdaCompliantYieldCurve yieldCurve,
      CdsAnalytic[] marketCDSs,
      CdsQuoteConvention[] quotes,
      double fracBumpAmount) {

    ArgChecker.notNull(cds, "cds");
    ArgChecker.noNulls(marketCDSs, "curvePoints");
    ArgChecker.noNulls(quotes, "quotes");
    ArgChecker.notNull(yieldCurve, "yieldCurve");
    ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
    int n = marketCDSs.length;
    ArgChecker.isTrue(n == quotes.length, "speads length does not match curvePoints");

    IsdaCompliantCreditCurve baseCurve =
        _curveBuilder.calibrateCreditCurve(marketCDSs, quotes, yieldCurve);
    double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
    double[] res = new double[n];
    for (int i = 0; i < n; i++) {
      CdsQuoteConvention[] bumpedQuotes =
          bumpQuoteAtIndex(marketCDSs, quotes, yieldCurve, fracBumpAmount, i);
      IsdaCompliantCreditCurve bumpedCurve =
          _curveBuilder.calibrateCreditCurve(marketCDSs, bumpedQuotes, yieldCurve);
      double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon);
      res[i] = (price - basePrice) / fracBumpAmount;
    }
    return res;
  }
  /**
   * The bucked CS01 (or credit DV01) on a set of CDSS by bumping each quoted (or flat) spread in
   * turn. This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their
   * quoted (or flat) spreads (expressed as <b>fractions not basis points</b>) and bootstraps a
   * credit (hazard) curve - the target CDS is then priced with this credit curve. This is then
   * repeated with each market spreads bumped in turn. The result is the vector of differences
   * (bumped minus base price) divided by the bump amount.<br>
   * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S_i}$$ for a flat curve
   * where $$S_i$$
   *
   * @param cds a set of analytic description of CDSs traded at a certain times
   * @param dealSpread The <b>fraction</b> spread of the CDS
   * @param yieldCurve The yield (or discount) curve
   * @param marketCDSs The market CDSs - these are the reference instruments used to build the
   *     credit curve
   * @param quotedSpreads The <b>fractional</b> spreads of the market CDSs
   * @param fracBumpAmount The fraction bump amount, so a 1pb bump is 1e-4
   * @param shiftType ABSOLUTE or RELATIVE
   * @return The bucked CS01 for a set of CDSs
   */
  public double[][] bucketedCS01FromQuotedSpreads(
      CdsAnalytic[] cds,
      double dealSpread,
      IsdaCompliantYieldCurve yieldCurve,
      CdsAnalytic[] marketCDSs,
      double[] quotedSpreads,
      double fracBumpAmount,
      ShiftType shiftType) {

    ArgChecker.noNulls(cds, "cds");
    ArgChecker.noNulls(marketCDSs, "curvePoints");
    ArgChecker.notEmpty(quotedSpreads, "spreads");
    ArgChecker.notNull(yieldCurve, "yieldCurve");
    ArgChecker.notNull(shiftType, "shiftType");
    ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
    int nMarketCDSs = marketCDSs.length;
    ArgChecker.isTrue(
        nMarketCDSs == quotedSpreads.length, "speads length does not match curvePoints");
    CdsPriceType priceType = CdsPriceType.DIRTY;
    double[] premiums = new double[nMarketCDSs];
    Arrays.fill(premiums, dealSpread); // assume the premiums of all CDS are equal

    int nTradeCDSs = cds.length;

    double[] puf =
        _pufConverter.quotedSpreadsToPUF(marketCDSs, premiums, yieldCurve, quotedSpreads);
    // TODO not needed
    IsdaCompliantCreditCurve baseCurve =
        _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, puf);
    double[] basePrices = new double[nTradeCDSs];
    for (int j = 0; j < nTradeCDSs; j++) {
      basePrices[j] = _pricer.pv(cds[j], yieldCurve, baseCurve, dealSpread, priceType);
    }

    double[] bumpedPUF = new double[nMarketCDSs];
    double[][] res = new double[nTradeCDSs][nMarketCDSs];

    for (int i = 0; i < nMarketCDSs; i++) { // Outer loop is over bumps
      System.arraycopy(puf, 0, bumpedPUF, 0, nMarketCDSs);
      double bumpedSpread = bumpedSpread(quotedSpreads[i], fracBumpAmount, shiftType);
      bumpedPUF[i] =
          _pufConverter.quotedSpreadToPUF(marketCDSs[i], premiums[i], yieldCurve, bumpedSpread);
      // TODO a lot of unnecessary recalibration here
      IsdaCompliantCreditCurve bumpedCurve =
          _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, bumpedPUF);
      for (int j = 0; j < nTradeCDSs; j++) {
        double price = _pricer.pv(cds[j], yieldCurve, bumpedCurve, dealSpread, priceType);
        res[j][i] = (price - basePrices[j]) / fracBumpAmount;
      }
    }
    return res;
  }
  private double fdCreditDV01(
      CdsAnalytic pricingCDS,
      double cdsSpread,
      CdsAnalytic[] curvePoints,
      double[] spreadsUp,
      double[] spreadsDown,
      IsdaCompliantYieldCurve yieldCurve,
      CdsPriceType priceType) {

    IsdaCompliantCreditCurve curveUp =
        _curveBuilder.calibrateCreditCurve(curvePoints, spreadsUp, yieldCurve);
    IsdaCompliantCreditCurve curveDown =
        _curveBuilder.calibrateCreditCurve(curvePoints, spreadsDown, yieldCurve);
    double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType);
    double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType);
    return up - down;
  }
  /**
   * The bucked CS01 (or credit DV01) by shifting each implied par-spread in turn. This takes an
   * extraneous yield curve and a credit curve and a set of bucket CDSs (CDSs with maturities equal
   * to the bucket points). Par-spreads at the bucket maturities are implied from the credit curve.
   * These spreads form pseudo market spreads to bootstraps a new credit (hazard) curve - the target
   * CDS is then priced with this credit curve. This is then repeated with each spreads bumped in
   * turn. The result is the vector of differences (bumped minus base price) divided by the bump
   * amount.
   *
   * @param cds analytic description of a CDS traded at a certain time
   * @param cdsCoupon The <b>fraction</b> spread of the CDS
   * @param bucketCDSs these are the reference instruments that correspond to maturity buckets
   * @param yieldCurve The yield (or discount) curve
   * @param creditCurve the credit curve
   * @param fracBumpAmount The fraction bump amount, so a 1pb bump is 1e-4
   * @return The credit DV01
   */
  public double[] bucketedCS01FromCreditCurve(
      CdsAnalytic cds,
      double cdsCoupon,
      CdsAnalytic[] bucketCDSs,
      IsdaCompliantYieldCurve yieldCurve,
      IsdaCompliantCreditCurve creditCurve,
      double fracBumpAmount) {

    ArgChecker.notNull(cds, "cds");
    ArgChecker.noNulls(bucketCDSs, "bucketCDSs");
    ArgChecker.notNull(creditCurve, "creditCurve");
    ArgChecker.notNull(yieldCurve, "yieldCurve");
    ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
    int n = bucketCDSs.length;

    double[] impSpreads = new double[n];
    double[] t = new double[n];
    for (int i = 0; i < n; i++) {
      impSpreads[i] = _pricer.parSpread(bucketCDSs[i], yieldCurve, creditCurve);
      t[i] = bucketCDSs[i].getProtectionEnd();
      if (i > 0) {
        ArgChecker.isTrue(t[i] > t[i - 1], "buckets must be assending");
      }
    }
    int index = Arrays.binarySearch(t, cds.getProtectionEnd());
    if (index < 0) {
      index = -1 - index;
    }
    index = Math.min(index, n - 1);

    IsdaCompliantCreditCurve baseCurve =
        _curveBuilder.calibrateCreditCurve(bucketCDSs, impSpreads, yieldCurve);
    double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
    double[] res = new double[n];
    for (int i = 0; i <= index; i++) { // don't bother calculating where there is no sensitivity
      double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, ShiftType.ABSOLUTE, i);
      IsdaCompliantCreditCurve bumpedCurve =
          _curveBuilder.calibrateCreditCurve(bucketCDSs, bumpedSpreads, yieldCurve);
      double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon);
      res[i] = (price - basePrice) / fracBumpAmount;
    }
    return res;
  }
  /**
   * The CS01 (or credit DV01) by a shift of the quoted (or flat) spread of the CDS <b>when the CDS
   * is quoted as points up-front (PUF)</b>.<br>
   * This simply converts the PUF quote to a quoted (or flat) spread then calls
   * parallelCS01FromQuotedSpread
   *
   * @param cds analytic description of a CDS traded at a certain time - it is this CDS that we are
   *     calculation CDV01 for
   * @param coupon the of the traded CDS (expressed as <b>fractions not basis points</b>)
   * @param yieldCurve The yield (or discount) curve
   * @param puf points up-front (as a fraction)
   * @param fracBumpAmount The fraction bump amount <b>of the quoted (or flat) spread</b>, so a 1pb
   *     bump is 1e-4
   * @return The credit DV01
   */
  public double parallelCS01FromPUF(
      CdsAnalytic cds,
      double coupon,
      IsdaCompliantYieldCurve yieldCurve,
      double puf,
      double fracBumpAmount) {

    double bumpedQSpread =
        _pufConverter.pufToQuotedSpread(cds, coupon, yieldCurve, puf) + fracBumpAmount;
    IsdaCompliantCreditCurve bumpedCurve =
        _curveBuilder.calibrateCreditCurve(cds, bumpedQSpread, yieldCurve);
    double bumpedPrice = _pricer.pv(cds, yieldCurve, bumpedCurve, coupon);
    return (bumpedPrice - puf) / fracBumpAmount;
  }
  public double parallelCS01FromCreditCurve(
      CdsAnalytic cds,
      double cdsCoupon,
      CdsAnalytic[] pillarCDSs,
      IsdaCompliantYieldCurve yieldCurve,
      IsdaCompliantCreditCurve creditCurve,
      double fracBumpAmount) {

    ArgChecker.notNull(cds, "cds");
    ArgChecker.noNulls(pillarCDSs, "pillarCDSs");
    ArgChecker.notNull(creditCurve, "creditCurve");
    ArgChecker.notNull(yieldCurve, "yieldCurve");
    ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
    int n = pillarCDSs.length;

    double[] impSpreads = new double[n];
    double[] t = new double[n];
    for (int i = 0; i < n; i++) {
      impSpreads[i] = _pricer.parSpread(pillarCDSs[i], yieldCurve, creditCurve);
      t[i] = pillarCDSs[i].getProtectionEnd();
      if (i > 0) {
        ArgChecker.isTrue(t[i] > t[i - 1], "pillars must be assending");
      }
    }

    IsdaCompliantCreditCurve baseCurve =
        _curveBuilder.calibrateCreditCurve(pillarCDSs, impSpreads, yieldCurve);
    double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
    double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, ShiftType.ABSOLUTE);
    IsdaCompliantCreditCurve bumpedCurve =
        _curveBuilder.calibrateCreditCurve(pillarCDSs, bumpedSpreads, yieldCurve);
    double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon);
    double res = (price - basePrice) / fracBumpAmount;

    return res;
  }