@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);
    }
  }
  @Override
  public Set<ComputedValue> execute(
      final FunctionExecutionContext executionContext,
      final FunctionInputs inputs,
      final ComputationTarget target,
      final Set<ValueRequirement> desiredValues)
      throws AsynchronousExecution {
    final ZonedDateTime now = ZonedDateTime.now(executionContext.getValuationClock());
    final ValueRequirement requirement = desiredValues.iterator().next();
    final ValueProperties properties = requirement.getConstraints().copy().get();

    final LegacyVanillaCDSSecurity security = (LegacyVanillaCDSSecurity) target.getSecurity();
    // LegacyVanillaCreditDefaultSwapDefinition cds =
    // _converter.visitLegacyVanillaCDSSecurity(security);
    final ValueRequirement desiredValue = desiredValues.iterator().next(); // all same constraints

    final String quoteConventionString =
        desiredValue.getConstraint(ISDAFunctionConstants.CDS_QUOTE_CONVENTION);
    final StandardCDSQuotingConvention quoteConvention =
        StandardCDSQuotingConvention.parse(quoteConventionString);

    final CdsRecoveryRateIdentifier recoveryRateIdentifier =
        security.accept(
            new CreditSecurityToRecoveryRateVisitor(executionContext.getSecuritySource()));
    Object recoveryRateObject =
        inputs.getValue(
            new ValueRequirement(
                "PX_LAST",
                ComputationTargetType.PRIMITIVE,
                recoveryRateIdentifier.getExternalId()));
    if (recoveryRateObject == null) {
      throw new OpenGammaRuntimeException("Could not get recovery rate");
      // s_logger.warn("Could not get recovery rate, defaulting to 0.4: " + recoveryRateIdentifier);
      // recoveryRateObject = 0.4;
    }
    final double recoveryRate = (Double) recoveryRateObject;

    // get the isda curve
    final Object isdaObject = inputs.getValue(ValueRequirementNames.YIELD_CURVE);
    if (isdaObject == null) {
      throw new OpenGammaRuntimeException("Couldn't get isda curve");
    }
    final ISDACompliantYieldCurve yieldCurve = (ISDACompliantYieldCurve) isdaObject;

    // spreads
    NodalTenorDoubleCurve spreadObject =
        (NodalTenorDoubleCurve) inputs.getValue(ValueRequirementNames.BUCKETED_SPREADS);
    if (spreadObject == null) {
      throw new OpenGammaRuntimeException("Unable to get spreads");
    }
    final double[] spreads = ArrayUtils.toPrimitive(spreadObject.getYData());
    // final String pillarString = IMMDateGenerator.isIMMDate(security.getMaturityDate()) ?
    // requirement.getConstraint(ISDAFunctionConstants.ISDA_BUCKET_TENORS) :
    // ISDACompliantCreditCurveFunction.NON_IMM_PILLAR_TENORS;
    final ZonedDateTime[] bucketDates =
        SpreadCurveFunctions.getPillarDates(now, spreadObject.getXData());
    final CDSQuoteConvention[] quotes =
        SpreadCurveFunctions.getQuotes(
            security.getMaturityDate(), spreads, security.getParSpread(), quoteConvention, false);

    // spreads
    NodalTenorDoubleCurve pillarObject =
        (NodalTenorDoubleCurve) inputs.getValue(ValueRequirementNames.PILLAR_SPREADS);
    if (pillarObject == null) {
      throw new OpenGammaRuntimeException("Unable to get pillars");
    }

    // CDS analytics for credit curve (possible performance improvement if earlier result obtained)
    // final LegacyVanillaCreditDefaultSwapDefinition curveCDS = cds.withStartDate(now);
    // security.setStartDate(now); // needed for curve instruments
    final CDSAnalytic[] bucketCDSs = new CDSAnalytic[bucketDates.length];
    for (int i = 0; i < bucketCDSs.length; i++) {
      // security.setMaturityDate(bucketDates[i]);
      final CDSAnalyticVisitor visitor =
          new CDSAnalyticVisitor(
              now.toLocalDate(),
              _holidaySource,
              _regionSource,
              security.getStartDate().toLocalDate(),
              bucketDates[i].toLocalDate(),
              recoveryRate);
      bucketCDSs[i] = security.accept(visitor);
    }

    final ZonedDateTime[] pillarDates =
        SpreadCurveFunctions.getPillarDates(now, pillarObject.getXData());
    final CDSAnalytic[] pillarCDSs = new CDSAnalytic[pillarDates.length];
    for (int i = 0; i < pillarCDSs.length; i++) {
      // security.setMaturityDate(bucketDates[i]);
      final CDSAnalyticVisitor visitor =
          new CDSAnalyticVisitor(
              now.toLocalDate(),
              _holidaySource,
              _regionSource,
              security.getStartDate().toLocalDate(),
              pillarDates[i].toLocalDate(),
              recoveryRate);
      pillarCDSs[i] = security.accept(visitor);
    }

    final ISDACompliantCreditCurve creditCurve =
        (ISDACompliantCreditCurve) inputs.getValue(ValueRequirementNames.HAZARD_RATE_CURVE);
    if (creditCurve == null) {
      throw new OpenGammaRuntimeException("Couldnt get credit curve");
    }

    // final CDSAnalytic analytic = CDSAnalyticConverter.create(cds, now.toLocalDate());
    final CDSAnalyticVisitor visitor =
        new CDSAnalyticVisitor(now.toLocalDate(), _holidaySource, _regionSource, recoveryRate);
    final CDSAnalytic analytic = security.accept(visitor);
    final BuySellProtection buySellProtection =
        security.isBuy() ? BuySellProtection.BUY : BuySellProtection.SELL;
    //    final String term = new Tenor(Period.between(security.getStartDate().toLocalDate(),
    // security.getMaturityDate().toLocalDate())).getPeriod().toString();
    //    final Double cdsQuoteDouble = (Double) inputs.getValue(new
    // ValueRequirement(MarketDataRequirementNames.MARKET_VALUE,
    //        ComputationTargetType.PRIMITIVE, ExternalId.of("Tenor", term)));
    final Double cdsQuoteDouble = (Double) inputs.getValue(MarketDataRequirementNames.MARKET_VALUE);
    if (cdsQuoteDouble == null) {
      throw new OpenGammaRuntimeException("Couldn't get spread for " + security);
    }
    final CDSQuoteConvention quote =
        SpreadCurveFunctions.getQuotes(
            security.getMaturityDate(),
            new double[] {cdsQuoteDouble},
            security.getParSpread(),
            quoteConvention,
            true)[0];

    boolean isNonIMMFAndFromPUF =
        !IMMDateLogic.isIMMDate(security.getMaturityDate().toLocalDate())
            && quote instanceof PointsUpFront;
    boolean isNonIMMAndFromSpread =
        !IMMDateLogic.isIMMDate(security.getMaturityDate().toLocalDate())
            && (quote instanceof QuotedSpread || quote instanceof ParSpread);
    int buySellPremiumFactor = security.isBuy() ? -1 : 1;

    final double notional = security.getNotional().getAmount();
    final double coupon = security.getParSpread() * ONE_BPS;
    final PointsUpFront puf =
        getPointsUpfront(quote, buySellProtection, yieldCurve, analytic, creditCurve);
    final double accruedPremiumPrim =
        isNonIMMAndFromSpread || isNonIMMFAndFromPUF ? 0 : analytic.getAccruedPremium(coupon);
    //    final double accruedPremium = isNonIMMAndFromSpread || isNonIMMFAndFromPUF ? 0 :
    // analytic.getAccruedPremium(coupon) * notional * buySellPremiumFactor;
    final double accruedPremium =
        isNonIMMAndFromSpread || isNonIMMFAndFromPUF
            ? 0
            : accruedPremiumPrim * notional * buySellPremiumFactor;
    final int accruedDays =
        isNonIMMAndFromSpread || isNonIMMFAndFromPUF ? 0 : analytic.getAccuredDays();
    final double quotedSpread =
        getQuotedSpread(quote, puf, buySellProtection, yieldCurve, analytic).getQuotedSpread();
    final double upfrontAmount =
        isNonIMMAndFromSpread
            ? 0
            : getUpfrontAmount(analytic, puf, notional, accruedPremiumPrim, buySellProtection);
    final double cleanPV = puf.getPointsUpFront() * notional;
    final double principal = isNonIMMAndFromSpread ? 0 : cleanPV;
    final double cleanPrice = getCleanPrice(puf);
    final TenorLabelledMatrix1D bucketedCS01 =
        getBucketedCS01(
            analytic,
            bucketCDSs,
            spreadObject.getXData(),
            quote,
            notional,
            yieldCurve,
            creditCurve);
    final double parallelCS01 =
        getParallelCS01(
            quote,
            analytic,
            yieldCurve,
            notional,
            pillarCDSs,
            ArrayUtils.toPrimitive(pillarObject.getYData()));

    final Set<ComputedValue> results = Sets.newHashSetWithExpectedSize(_valueRequirements.length);
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.ACCRUED_PREMIUM, target.toSpecification(), properties),
            accruedPremium));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.ACCRUED_DAYS, target.toSpecification(), properties),
            accruedDays));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.QUOTED_SPREAD, target.toSpecification(), properties),
            quotedSpread / ONE_BPS));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.UPFRONT_AMOUNT, target.toSpecification(), properties),
            upfrontAmount));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.DIRTY_PRESENT_VALUE, target.toSpecification(), properties),
            upfrontAmount));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.CLEAN_PRESENT_VALUE, target.toSpecification(), properties),
            cleanPV));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.PRINCIPAL, target.toSpecification(), properties),
            principal));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.CLEAN_PRICE, target.toSpecification(), properties),
            cleanPrice));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.BUCKETED_CS01, target.toSpecification(), properties),
            bucketedCS01));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.PARALLEL_CS01, target.toSpecification(), properties),
            parallelCS01));
    results.add(
        new ComputedValue(
            new ValueSpecification(
                ValueRequirementNames.POINTS_UPFRONT, target.toSpecification(), properties),
            puf.getPointsUpFront()));
    return results;
  }
  @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);
    }
  }