/**
  * Return a good business date computed from a given date and shifted by a certain number of
  * business days. This version uses LocalDate. If the number of shift days is 0, the return date
  * is the next business day. If the number of shift days is non-zero (positive or negative), a 0
  * shift is first applied and then a one business day shift is applied as many time as the
  * absolute value of the shift. If the shift is positive, the one business day is to the future.,
  * if the shift is negative, the one business day is to the past.
  *
  * @param date The initial date.
  * @param shiftDays The number of days of the adjustment. Can be negative or positive.
  * @param calendar The calendar representing the good business days.
  * @return The adjusted dates.
  */
 public static LocalDate getAdjustedDate(
     final LocalDate date, final int shiftDays, final Calendar calendar) {
   ArgumentChecker.notNull(date, "date");
   ArgumentChecker.notNull(calendar, "calendar");
   LocalDate result = date;
   while (!calendar.isWorkingDay(result)) {
     result = result.plusDays(1);
   }
   if (shiftDays > 0) {
     for (int loopday = 0; loopday < shiftDays; loopday++) {
       result = result.plusDays(1);
       while (!calendar.isWorkingDay(result)) {
         result = result.plusDays(1);
       }
     }
   } else {
     for (int loopday = 0; loopday < -shiftDays; loopday++) {
       result = result.minusDays(1);
       while (!calendar.isWorkingDay(result)) {
         result = result.minusDays(1);
       }
     }
   }
   return result;
 }
  /**
   * Expiry date of Brent crude Futures: The business day preceding the 15th day of the contract
   * month. See
   * http://www.cmegroup.com/trading/energy/crude-oil/brent-crude-oil-last-day_contractSpecs_futures.html#prodType=AME
   * Note: Logic to handle holidays in London is not handled currently
   *
   * @param n n'th expiry date after today
   * @param today valuation date
   * @param holidayCalendar holiday calendar
   * @return True expiry date of the option
   */
  @Override
  public LocalDate getExpiryDate(
      final int n, final LocalDate today, final Calendar holidayCalendar) {
    ArgumentChecker.isTrue(n > 0, "n must be greater than zero; have {}", n);
    ArgumentChecker.notNull(today, "today");
    ArgumentChecker.notNull(holidayCalendar, "holiday calendar");

    LocalDate expiryDate = getExpiryMonth(n, today);

    while (!holidayCalendar.isWorkingDay(expiryDate)) {
      expiryDate = expiryDate.minusDays(1);
    }
    return expiryDate;
  }
  /**
   * Expiry date of Soybean Futures: The 3rd last business day of the month. See
   * http://www.cmegroup.com/trading/metals/precious/gold_contract_specifications.html
   *
   * @param n the n'th expiry date after today, greater than zero
   * @param today the valuation date, not null
   * @param holidayCalendar the holiday calendar, not null
   * @return the expiry date, not null
   */
  @Override
  public LocalDate getExpiryDate(
      final int n, final LocalDate today, final Calendar holidayCalendar) {
    ArgumentChecker.isTrue(n > 0, "n must be greater than zero; have {}", n);
    ArgumentChecker.notNull(today, "today");
    ArgumentChecker.notNull(holidayCalendar, "holiday calendar");

    LocalDate expiryDate = getExpiryMonth(n, today).with(LAST_DAY_ADJUSTER);
    int nBusinessDays = 3;
    if (holidayCalendar.isWorkingDay(expiryDate)) {
      nBusinessDays--;
    }
    // go back to 3 business days
    while (nBusinessDays > 0) {
      expiryDate = expiryDate.minusDays(1);
      if (holidayCalendar.isWorkingDay(expiryDate)) {
        nBusinessDays--;
      }
    }
    return expiryDate;
  }
  public void getHistoricalWithInclusiveExclusiveDates() throws Exception {
    LocalDate end = DateUtils.previousWeekDay();
    LocalDate start = end.minusDays(7);

    HistoricalTimeSeriesInfoSearchRequest request =
        new HistoricalTimeSeriesInfoSearchRequest(IDENTIFIERS);
    request.setValidityDate(LocalDate.now());
    request.setDataSource(BBG_DATA_SOURCE);
    request.setDataProvider(CMPL_DATA_PROVIDER);
    request.setDataField(CLOSE_DATA_FIELD);
    LocalDateDoubleTimeSeries timeSeries = randomTimeSeries();

    HistoricalTimeSeriesInfoSearchResult searchResult = new HistoricalTimeSeriesInfoSearchResult();
    HistoricalTimeSeriesInfoDocument doc = new HistoricalTimeSeriesInfoDocument();
    doc.setUniqueId(UID);
    doc.getInfo().setTimeSeriesObjectId(UID.getObjectId());
    searchResult.getDocuments().add(doc);

    when(_mockResolver.resolve(
            IDENTIFIERS,
            LocalDate.now(),
            BBG_DATA_SOURCE,
            CMPL_DATA_PROVIDER,
            CLOSE_DATA_FIELD,
            null))
        .thenReturn(new HistoricalTimeSeriesResolutionResult(doc.getInfo()));

    for (boolean includeStart : new boolean[] {true, false}) {
      for (boolean includeEnd : new boolean[] {true, false}) {
        // Also test max points limit for various values
        for (Integer maxPoints : new Integer[] {null, -10, -1, 1, 0, -2, 2, 10}) {
          LocalDate startInput = start;
          LocalDate endInput = end;
          if (!includeStart) {
            startInput = start.plusDays(1);
          }
          if (!includeEnd) {
            endInput = end.minusDays(1);
          }

          ManageableHistoricalTimeSeries hts = new ManageableHistoricalTimeSeries();
          LocalDateDoubleTimeSeries lddts =
              (maxPoints == null)
                      || (Math.abs(maxPoints)
                          >= timeSeries.subSeries(start, includeStart, end, includeEnd).size())
                  ? timeSeries.subSeries(start, includeStart, end, includeEnd)
                  : (maxPoints >= 0)
                      ? timeSeries.subSeries(start, includeStart, end, includeEnd).head(maxPoints)
                      : timeSeries.subSeries(start, includeStart, end, includeEnd).tail(-maxPoints);
          hts.setUniqueId(UID);
          hts.setTimeSeries(lddts);
          when(_mockMaster.getTimeSeries(
                  UID.getObjectId(),
                  VersionCorrection.LATEST,
                  HistoricalTimeSeriesGetFilter.ofRange(startInput, endInput, maxPoints)))
              .thenReturn(hts);
          when(_mockMaster.search(request)).thenReturn(searchResult);

          HistoricalTimeSeries test =
              (maxPoints == null)
                  ? _tsSource.getHistoricalTimeSeries(
                      IDENTIFIERS,
                      BBG_DATA_SOURCE,
                      CMPL_DATA_PROVIDER,
                      CLOSE_DATA_FIELD,
                      start,
                      includeStart,
                      end,
                      includeEnd)
                  : _tsSource.getHistoricalTimeSeries(
                      IDENTIFIERS,
                      BBG_DATA_SOURCE,
                      CMPL_DATA_PROVIDER,
                      CLOSE_DATA_FIELD,
                      start,
                      includeStart,
                      end,
                      includeEnd,
                      maxPoints);

          assertEquals(UID, test.getUniqueId());
          assertEquals(hts.getTimeSeries(), test.getTimeSeries());
        }
      }
    }
  }
  /** Test of CDX.NA.IG.20-v1 5Y from Markit website */
  @Test
  public void test() {
    // numbers from https://www.markit.com
    // expected values (user)
    final double mCleanPrice = 100.3;
    final double mCashSettle = -43750455922031.0;
    final int mAccDays = 49;
    final double mAccAmt = 13611111111111.11;
    final double mCreditDV01 = 4647028138242.0;
    // transformed
    final double mCashSettleTransformed = -43757966062423.0;
    final double mCreditDV01Transformed = 4646838148143.0;

    final double tradeLevel = 0.00935;
    final LocalDate tradeDate = LocalDate.of(2013, Month.AUGUST, 7);
    final LocalDate stepinDate = tradeDate.plusDays(1); // AKA stepin date
    final LocalDate cashSettleDate =
        addWorkDays(tradeDate, 3, DEFAULT_CALENDAR); // AKA valuation date
    final LocalDate startDate = getPrevIMMDate(tradeDate);
    final LocalDate maturity = LocalDate.of(2018, Month.JUNE, 20);

    // yield curve
    final LocalDate spotDate = addWorkDays(tradeDate.minusDays(1), 3, DEFAULT_CALENDAR);
    final String[] yieldCurvePoints =
        new String[] {
          "1M", "2M", "3M", "6M", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y",
          "12Y", "15Y", "20Y", "25Y", "30Y"
        };
    final String[] yieldCurveInstruments =
        new String[] {
          "M", "M", "M", "M", "M", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S",
          "S"
        };
    final double[] rates =
        new double[] {
          0.00185, 0.00227, 0.002664, 0.003955, 0.006654, 0.004845, 0.00784, 0.011725, 0.0157,
          0.01919, 0.02219, 0.024565, 0.02657, 0.02825, 0.03095, 0.033495, 0.035505, 0.036425,
          0.036915
        };
    final ISDACompliantYieldCurve yieldCurve =
        makeYieldCurve(
            tradeDate,
            spotDate,
            yieldCurvePoints,
            yieldCurveInstruments,
            rates,
            ACT360,
            D30360,
            Period.ofMonths(6));

    final LocalDate nextIMM = getNextIMMDate(tradeDate);
    final LocalDate[] pillarDates = getIMMDateSet(nextIMM, TENORS);
    final int nPillars = pillarDates.length;
    final double[] flatSpreads = new double[nPillars];
    Arrays.fill(flatSpreads, tradeLevel);
    final CDSAnalytic[] calibrationCDS = new CDSAnalytic[nPillars];
    for (int i = 0; i < nPillars; i++) {
      calibrationCDS[i] =
          new CDSAnalytic(
              tradeDate,
              stepinDate,
              cashSettleDate,
              startDate,
              pillarDates[i],
              PAY_ACC_ON_DEFAULT,
              PAYMENT_INTERVAL,
              STUB,
              PROCTECTION_START,
              RECOVERY_RATE);
    }

    final CDSAnalytic pointCDS =
        new CDSAnalytic(
            tradeDate,
            stepinDate,
            cashSettleDate,
            startDate,
            maturity,
            PAY_ACC_ON_DEFAULT,
            PAYMENT_INTERVAL,
            STUB,
            PROCTECTION_START,
            RECOVERY_RATE);
    final QuotedSpread qSpread = new QuotedSpread(COUPON, tradeLevel);
    final double puf = PUF_CONVERTER.convert(pointCDS, qSpread, yieldCurve).getPointsUpFront();
    final double price = (1 - puf) * 100;
    final double accAmt = NOTIONAL * pointCDS.getAccruedPremium(COUPON);
    final double cashSettle = puf * NOTIONAL - accAmt;
    final double cs01 =
        NOTIONAL * ONE_BP * CS01_CAL.parallelCS01(pointCDS, qSpread, yieldCurve, ONE_BP);

    //    System.out.println("price: " + price + "%");
    //    System.out.println("Accured Days: " + pointCDS.getAccuredDays());
    //    System.out.println("Accured Amt: " + accAmt);
    //    System.out.println("Cash Settlement: " + cashSettle);
    //    System.out.println("Credit DV01: " + cs01);

    assertEquals("price", mCleanPrice, price, 1e-1); // only 1dp of percentage given
    assertEquals("Cash Settlement", mCashSettle, cashSettle, 1e-15 * NOTIONAL);
    assertEquals("Accured Days", mAccDays, pointCDS.getAccuredDays());
    assertEquals("Accured Amt", mAccAmt, accAmt, 1e-18 * NOTIONAL);
    assertEquals("Credit DV01", mCreditDV01, cs01, 1e-15 * NOTIONAL);

    // flat spread term structure (transformed)
    final ISDACompliantCreditCurve creditCurve =
        CREDIT_CURVE_BUILDER.calibrateCreditCurve(calibrationCDS, flatSpreads, yieldCurve);
    final double cashSettleTrans =
        NOTIONAL * PRICER.pv(pointCDS, yieldCurve, creditCurve, COUPON, PriceType.DIRTY);
    final double cs01Trans =
        NOTIONAL
            * ONE_BP
            * CS01_CAL.parallelCS01FromParSpreads(
                pointCDS,
                COUPON,
                yieldCurve,
                calibrationCDS,
                flatSpreads,
                ONE_BP,
                BumpType.ADDITIVE);
    //    System.out.println("Cash Settlement (trans): " + cashSettleTrans);
    //    System.out.println("Credit DV01 (trans): " + cs01Trans);
    assertEquals(
        "Cash Settlement (trans)", mCashSettleTransformed, cashSettleTrans, 1e-15 * NOTIONAL);
    assertEquals("Credit DV01 (Trans)", mCreditDV01Transformed, cs01Trans, 1e-15 * NOTIONAL);
  }
  @Test(enabled = false)
  public void rollingTest() {

    final MarketQuoteConverter pufConverter = new MarketQuoteConverter();
    final FastCreditCurveBuilder builder = new FastCreditCurveBuilder();

    final double notional = 1e12;
    final LocalDate today = LocalDate.of(2011, Month.JUNE, 13);
    final Period tenor = Period.ofYears(3);
    final double tradeLevel = 99.785 * ONE_BP;
    // final double tradeLevel = 99.78471 * ONE_BP;

    final LocalDate tradeDate = today;
    final LocalDate stepinDate = tradeDate.plusDays(1); // AKA stepin date
    final LocalDate cashSettleDate =
        addWorkDays(tradeDate, 3, DEFAULT_CALENDAR); // AKA valuation date
    final LocalDate startDate = getPrevIMMDate(tradeDate).plusDays(1);
    final LocalDate nextRolldate = getNextIndexRollDate(today);
    final LocalDate maturity = nextRolldate.plus(tenor).minusMonths(3);

    // yield curve
    final LocalDate spotDate = addWorkDays(today.minusDays(1), 3, DEFAULT_CALENDAR);
    final String[] yieldCurvePoints =
        new String[] {
          "1M", "2M", "3M", "6M", "9M", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y",
          "12Y", "15Y", "20Y", "30Y"
        };
    final String[] yieldCurveInstruments =
        new String[] {
          "M", "M", "M", "M", "M", "M", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S",
          "S"
        };
    final double[] rates =
        new double[] {
          0.01262, 0.01344, 0.01469, 0.01739, 0.01947, 0.02145, 0.02114, 0.02308, 0.02511, 0.02695,
          0.02857, 0.02989, 0.03104, 0.03204, 0.03292, 0.0345, 0.03619, 0.03712, 0.03602
        };
    final ISDACompliantYieldCurve yieldCurve =
        makeYieldCurve(
            tradeDate,
            spotDate,
            yieldCurvePoints,
            yieldCurveInstruments,
            rates,
            ACT360,
            D30360,
            Period.ofYears(1));

    final CDSAnalytic pointCDS =
        new CDSAnalytic(
            tradeDate,
            stepinDate,
            cashSettleDate,
            startDate,
            maturity,
            PAY_ACC_ON_DEFAULT,
            PAYMENT_INTERVAL,
            STUB,
            PROCTECTION_START,
            RECOVERY_RATE);
    final QuotedSpread qSpread = new QuotedSpread(COUPON, tradeLevel);
    final double puf = pufConverter.convert(pointCDS, qSpread, yieldCurve).getPointsUpFront();
    final double accAmt = notional * pointCDS.getAccruedPremium(COUPON);
    final double cashAmount = notional * puf - accAmt;
    System.out.println(
        startDate
            + "\t"
            + maturity
            + "\t"
            + puf
            + "\t"
            + (1 - puf) * 100
            + "%\t"
            + cashAmount
            + "\t"
            + pointCDS.getAccuredDays()
            + "\t"
            + accAmt);

    final double impSpread = pufConverter.pufToQuotedSpread(pointCDS, COUPON, yieldCurve, puf);
    System.out.println("imp Spread: " + impSpread);

    // flat spread calculations
    final Period[] standardTenors =
        new Period[] {
          Period.ofMonths(6),
          Period.ofYears(1),
          Period.ofYears(2),
          Period.ofYears(3),
          Period.ofYears(4),
          Period.ofYears(5),
          Period.ofYears(7),
          Period.ofYears(10)
        };
    final int nMat = standardTenors.length;
    final LocalDate[] maturities = new LocalDate[nMat];
    final CDSAnalytic[] pillarCDS = new CDSAnalytic[nMat];
    for (int i = 0; i < nMat; i++) {
      maturities[i] = nextRolldate.plus(standardTenors[i]).minusMonths(3);
      pillarCDS[i] =
          new CDSAnalytic(
              tradeDate,
              stepinDate,
              cashSettleDate,
              startDate,
              maturities[i],
              PAY_ACC_ON_DEFAULT,
              PAYMENT_INTERVAL,
              STUB,
              PROCTECTION_START,
              RECOVERY_RATE);
    }
    final double[] flatSpreads = new double[nMat];
    Arrays.fill(flatSpreads, tradeLevel);
    final ISDACompliantCreditCurve creditCurve =
        builder.calibrateCreditCurve(pillarCDS, flatSpreads, yieldCurve);
    final double pufTrans = PRICER_MARKIT_FIX.pv(pointCDS, yieldCurve, creditCurve, COUPON);
    final double cashAmountTrans = notional * pufTrans - accAmt;
    System.out.println(pufTrans + "\t" + cashAmountTrans);
  }
  /**
   * This is the present value of the premium leg per unit of fractional spread - hence it is equal
   * to 10,000 times the RPV01 (Risky PV01). The actual PV of the leg is this multiplied by the
   * notional and the fractional spread (i.e. spread in basis points divided by 10,000)
   *
   * <p>This mimics the ISDA c function <b>JpmcdsCdsFeeLegPV</b>
   *
   * @param today The 'current' date
   * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka
   *     assignment date or effective date.
   * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3
   *     business days. Aka cash-settle date.
   * @param startDate The protection start date. If protectStart = true, then protections starts at
   *     the beginning of the day, otherwise it is at the end.
   * @param endDate The protection end date (the protection ends at end of day)
   * @param payAccOnDefault Is the accrued premium paid in the event of a default
   * @param tenor The nominal step between premium payments (e.g. 3 months, 6 months).
   * @param stubType stubType Options are FRONTSHORT, FRONTLONG, BACKSHORT, BACKLONG or NONE -
   *     <b>Note</b> in this code NONE is not allowed
   * @param yieldCurve Curve from which payments are discounted
   * @param hazardRateCurve Curve giving survival probability
   * @param protectStart Does protection start at the beginning of the day
   * @param priceType Clean or Dirty price. The clean price removes the accrued premium if the trade
   *     is between payment times.
   * @return 10,000 times the RPV01 (on a notional of 1)
   */
  public double pvPremiumLegPerUnitSpread(
      final LocalDate today,
      final LocalDate stepinDate,
      final LocalDate valueDate,
      final LocalDate startDate,
      final LocalDate endDate,
      final boolean payAccOnDefault,
      final Period tenor,
      final StubType stubType,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve,
      final boolean protectStart,
      final PriceType priceType) {
    ArgumentChecker.notNull(today, "null today");
    ArgumentChecker.notNull(stepinDate, "null stepinDate");
    ArgumentChecker.notNull(valueDate, "null valueDate");
    ArgumentChecker.notNull(startDate, "null startDate");
    ArgumentChecker.notNull(endDate, "null endDate");
    ArgumentChecker.notNull(tenor, "null tenor");
    ArgumentChecker.notNull(stubType, "null stubType");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");
    ArgumentChecker.notNull(priceType, "null priceType");
    ArgumentChecker.isFalse(valueDate.isBefore(today), "Require valueDate >= today");
    ArgumentChecker.isFalse(stepinDate.isBefore(today), "Require stepin >= today");

    final ISDAPremiumLegSchedule paymentSchedule =
        new ISDAPremiumLegSchedule(
            startDate,
            endDate,
            tenor,
            stubType,
            _businessdayAdjustmentConvention,
            _calandar,
            protectStart);
    final int nPayments = paymentSchedule.getNumPayments();

    // these are potentially different from startDate and endDate
    final LocalDate globalAccStart = paymentSchedule.getAccStartDate(0);
    final LocalDate golobalAccEnd = paymentSchedule.getAccEndDate(nPayments - 1);

    // TODO this logic could be part of ISDAPremiumLegSchdule
    final LocalDate matDate = protectStart ? golobalAccEnd.minusDays(1) : golobalAccEnd;

    if (today.isAfter(matDate) || stepinDate.isAfter(matDate)) {
      return 0.0; // trade has expired
    }

    final LocalDate[] yieldCurveDates = yieldCurve.getCurveDates();
    final LocalDate[] creditCurveDates = hazardRateCurve.getCurveDates();
    // This is common to the protection leg
    final LocalDate[] integrationSchedule =
        payAccOnDefault
            ? getIntegrationNodesAsDates(
                globalAccStart, golobalAccEnd, yieldCurveDates, creditCurveDates)
            : null;
    final int obsOffset = protectStart ? -1 : 0; // protection start at the beginning or end day

    double rpv01 = 0.0;
    for (int i = 0; i < nPayments; i++) {

      final LocalDate accStart = paymentSchedule.getAccStartDate(i);
      final LocalDate accEnd = paymentSchedule.getAccEndDate(i);
      final LocalDate pay = paymentSchedule.getPaymentDate(i);

      if (!accEnd.isAfter(stepinDate)) {
        continue; // this cashflow has already been realised
      }

      final double[] temp =
          calculateSinglePeriodRPV01(
              today, accStart, accEnd, pay, obsOffset, yieldCurve, hazardRateCurve);
      rpv01 += temp[0];

      if (payAccOnDefault) {
        final LocalDate offsetStepinDate = stepinDate.plusDays(obsOffset);
        final LocalDate offsetAccStartDate = accStart.plusDays(obsOffset);
        final LocalDate offsetAccEndDate = accEnd.plusDays(obsOffset);
        rpv01 +=
            calculateSinglePeriodAccrualOnDefault(
                today,
                offsetStepinDate,
                offsetAccStartDate,
                offsetAccEndDate,
                temp[1],
                yieldCurve,
                hazardRateCurve,
                integrationSchedule);
      }
    }

    // Compute the discount factor discounting the upfront payment made on the cash settlement date
    // back to the valuation date
    final double t = _curveDayCount.getDayCountFraction(today, valueDate);
    final double df = yieldCurve.getDiscountFactor(t);
    rpv01 /= df;

    // Do we want to calculate the clean price (includes the previously accrued portion of the
    // premium)
    if (priceType == PriceType.CLEAN) {
      rpv01 -= calculateAccruedInterest(paymentSchedule, stepinDate);
    }

    return rpv01;
  }
  /**
   * Get the value of the protection leg for unit notional
   *
   * <p>This mimics the ISDA c function <b>JpmcdsCdsContingentLegPV</b>
   *
   * @param today The 'current' date
   * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka
   *     assignment date or effective date.
   * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3
   *     business days. Aka cash-settle date.
   * @param startDate The protection start date. If protectStart = true, then protections starts at
   *     the beginning of the day, otherwise it is at the end.
   * @param endDate The protection end date (the protection ends at end of day)
   * @param yieldCurve Curve from which payments are discounted
   * @param hazardRateCurve Curve giving survival probability
   * @param recoveryRate The recovery rate of the protected debt
   * @param protectStart Does protection start at the beginning of the day
   * @return unit notional PV of protection (or contingent) leg
   */
  public double calculateProtectionLeg(
      final LocalDate today,
      final LocalDate stepinDate,
      final LocalDate valueDate,
      final LocalDate startDate,
      final LocalDate endDate,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve,
      final double recoveryRate,
      final boolean protectStart) {
    ArgumentChecker.notNull(today, "null today");
    ArgumentChecker.notNull(valueDate, "null valueDate");
    ArgumentChecker.notNull(startDate, "null startDate");
    ArgumentChecker.notNull(endDate, "null endDate");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");
    ArgumentChecker.isInRangeInclusive(0, 1.0, recoveryRate);
    ArgumentChecker.isFalse(valueDate.isBefore(today), "Require valueDate >= today");
    ArgumentChecker.isFalse(stepinDate.isBefore(today), "Require stepin >= today");

    if (recoveryRate == 1.0) {
      return 0.0;
    }

    final LocalDate temp = stepinDate.isAfter(startDate) ? stepinDate : startDate;
    final LocalDate effectiveStartDate = protectStart ? temp.minusDays(1) : temp;

    if (!endDate.isAfter(effectiveStartDate)) {
      return 0.0; // the protection has expired
    }

    final LocalDate[] yieldCurveDates = yieldCurve.getCurveDates();
    final LocalDate[] creditCurveDates = hazardRateCurve.getCurveDates();
    final double[] integrationSchedule =
        ISDACompliantScheduleGenerator.getIntegrationNodesAsTimes(
            today, effectiveStartDate, endDate, yieldCurveDates, creditCurveDates);

    // double s1 = hazardRateCurve.getSurvivalProbability(integrationSchedule[0]);
    // double df1 = yieldCurve.getDiscountFactor(integrationSchedule[0]);

    double ht1 = hazardRateCurve.getRT(integrationSchedule[0]);
    double rt1 = yieldCurve.getRT(integrationSchedule[0]);
    double s1 = Math.exp(-ht1);
    double p1 = Math.exp(-rt1);
    double pv = 0.0;
    final int n = integrationSchedule.length;
    for (int i = 1; i < n; ++i) {

      final double ht0 = ht1;
      final double rt0 = rt1;
      final double p0 = p1;
      final double s0 = s1;

      ht1 = hazardRateCurve.getRT(integrationSchedule[i]);
      rt1 = yieldCurve.getRT(integrationSchedule[i]);
      s1 = Math.exp(-ht1);
      p1 = Math.exp(-rt1);
      final double dht = ht1 - ht0;
      final double drt = rt1 - rt0;
      final double dhrt = dht + drt;

      // this is equivalent to the ISDA code without explicitly calculating the time step - it also
      // handles the limit
      double dPV;
      if (Math.abs(dhrt) < 1e-5) {
        dPV = dht * (1 - dhrt * (0.5 - dhrt / 6)) * p0 * s0;
      } else {
        dPV = dht / dhrt * (p0 * s0 - p1 * s1);
      }

      // *************
      // ISDA code
      // **************
      // final double dt = integrationSchedule[i] - integrationSchedule[i - 1];
      // final double s0 = s1;
      // final double df0 = df1;
      // s1 = hazardRateCurve.getSurvivalProbability(integrationSchedule[i]);
      // df1 = yieldCurve.getDiscountFactor(integrationSchedule[i]);
      // final double hazardRate = Math.log(s0 / s1) / dt;
      // final double interestRate = Math.log(df0 / df1) / dt;
      // pv += (hazardRate / (hazardRate + interestRate)) * (1.0 - Math.exp(-(hazardRate +
      // interestRate) * dt)) * s0 * df0;

      pv += dPV;
    }
    pv *= 1.0 - recoveryRate;
    // System.out.println(pv);

    // Compute the discount factor discounting the upfront payment made on the cash settlement date
    // back to the valuation date
    final double t = _curveDayCount.getDayCountFraction(today, valueDate);
    final double df = yieldCurve.getDiscountFactor(t);
    pv /= df;

    return pv;
  }