@Test
  public void consistencyTest() {
    final double tol = 1.e-12;

    final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1);
    final double timeToExpiry = ACT365F.yearFraction(TRADE_DATE, optionExpiry);
    final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5));
    final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry);

    final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652};
    final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS);

    final IntrinsicIndexDataBundle adjCurves =
        PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
    final double fwdSpread =
        INDEX_CAL.defaultAdjustedForwardSpread(
            fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves);
    final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves);
    final BlackIndexOptionPricer pricerWithFwd =
        new BlackIndexOptionPricer(
            fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
    final BlackIndexOptionPricer pricerNoFwd =
        new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurves);

    final boolean[] payer = new boolean[] {true, false};
    final double vol = 0.4;
    final double strikeSpread = 0.015;
    final ISDACompliantYieldCurve fwdYC = YIELD_CURVE.withOffset(timeToExpiry);
    final double strikePrice =
        CONVERTER.quotedSpreadToPUF(fwdCDX, INDEX_COUPON, fwdYC, strikeSpread);
    final IndexOptionStrike exPriceAmount = new ExerciseAmount(strikePrice);
    final IndexOptionStrike exSpreadAmount = new SpreadBasedStrike(strikeSpread);

    for (int i = 0; i < 2; ++i) {
      /** Consistency between option pricing methods */
      final double premFromPrice = pricerWithFwd.getOptionPremium(exPriceAmount, vol, payer[i]);
      final double premFromSpread = pricerWithFwd.getOptionPremium(exSpreadAmount, vol, payer[i]);
      final double premFromPriceBare =
          pricerWithFwd.getOptionPriceForPriceQuotedIndex(strikePrice, vol, payer[i]);
      final double premFromSpreadBare =
          pricerWithFwd.getOptionPriceForSpreadQuotedIndex(strikeSpread, vol, payer[i]);
      assertEquals(premFromPrice, premFromSpread, tol);
      assertEquals(premFromSpread, premFromPriceBare, tol);
      assertEquals(premFromPriceBare, premFromSpreadBare, tol);

      /** Consistency between constructors */
      final double PremFromPriceNoFwd = pricerNoFwd.getOptionPremium(exPriceAmount, vol, payer[i]);
      assertEquals(premFromPrice, PremFromPriceNoFwd, tol);

      /** Consistency with implied vol */
      final double vol1 =
          pricerWithFwd.getImpliedVolatility(exPriceAmount, premFromPrice, payer[i]);
      final double vol2 =
          pricerWithFwd.getImpliedVolatility(exSpreadAmount, premFromSpread, payer[i]);
      final double vol3 =
          pricerWithFwd.getImpliedVolForExercisePrice(strikePrice, premFromPriceBare, payer[i]);
      final double vol4 =
          pricerWithFwd.getImpliedVolForSpreadStrike(strikeSpread, premFromSpreadBare, payer[i]);
      assertEquals(vol, vol1, tol);
      assertEquals(vol, vol2, tol);
      assertEquals(vol, vol3, tol);
      assertEquals(vol, vol4, tol);
    }
  }
  @SuppressWarnings("unused")
  @Test
  public void limitTest() {
    final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1);
    final double timeToExpiry = ACT365F.yearFraction(TRADE_DATE, optionExpiry);
    final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5));
    final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry);

    final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652};
    final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS);

    final IntrinsicIndexDataBundle adjCurves =
        PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
    final double fwdSpread =
        INDEX_CAL.defaultAdjustedForwardSpread(
            fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves);
    final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves);

    final BlackIndexOptionPricer pricerWithFwd =
        new BlackIndexOptionPricer(
            fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
    final double modStrikeLimit = INDEX_COUPON + fwdCDX.getLGD() / fwdAnnuity;
    final double vol = 0.4;
    final double payLargeSpLimit =
        fwdAnnuity
            * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, true);
    final double recLargeSpLimit =
        fwdAnnuity
            * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, false);

    final double largeStrikeSp = 75.0;
    final double payLargeStrikeSp =
        pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, true);
    final double recLargeStrikeSp =
        pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, false);
    assertEquals(payLargeSpLimit, payLargeStrikeSp, 1.e-12);
    assertEquals(
        recLargeSpLimit,
        recLargeStrikeSp,
        1.e-3); // larger strike spread ends up with failure in root-finding

    /** Exception expected */
    final double negativeTime = -0.5;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, negativeTime, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("timeToExpiry must be positive. Value given " + negativeTime, e.getMessage());
    }
    try {
      new BlackIndexOptionPricer(
          fwdStartingCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("fwdCDS should be a Forward CDS", e.getMessage());
    }
    final double negativeCoupon = -150.0 * 1.0e-4;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, negativeCoupon, fwdSpread, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("indexCoupon must be positive", e.getMessage());
    }
    final double negativeFwdSp = -0.5;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, negativeFwdSp, fwdAnnuity);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("defaultAdjustedFwdSpread must be positive", e.getMessage());
    }
    final double negativeAnn = -0.3;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, negativeAnn);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals("pvFwdAnnuity must be positive", e.getMessage());
    }
    final double largeAnn = fwdCDX.getProtectionEnd() * 2.0;
    try {
      new BlackIndexOptionPricer(
          fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, largeAnn);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertEquals(
          "Value of annuity of "
              + largeAnn
              + " is greater than length (in years) of forward CDS. Annuity should be given for unit notional",
          e.getMessage());
    }

    final double smallStrike = -0.9;
    try {
      pricerWithFwd.getOptionPriceForPriceQuotedIndex(smallStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
    final double largeStrike = 0.9;
    try {
      pricerWithFwd.getOptionPriceForPriceQuotedIndex(largeStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
    try {
      pricerWithFwd.getOptionPriceForSpreadQuotedIndex(smallStrike, vol, true);
      throw new RuntimeException();
    } catch (final Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
  }