@Test
  public void sensitivityTest() {
    final VolatilitySurfaceProvider vsPro =
        new BasisSplineVolatilitySurfaceProvider(0.0, 0.15, 10, 3, 0.0, 10.0, 7, 2);

    final DoubleMatrix1D w = new DoubleMatrix1D(vsPro.getNumModelParameters());
    for (int i = 0; i < w.getNumberOfElements(); i++) {
      w.getData()[i] = RANDOM.nextDouble();
    }

    final VolatilitySurface volSurf = vsPro.getVolSurface(w);
    final Surface<Double, Double, DoubleMatrix1D> senseSurf =
        vsPro.getParameterSensitivitySurface(w);
    final Surface<Double, Double, Pair<Double, DoubleMatrix1D>> volAndSenseSurf =
        vsPro.getVolAndParameterSensitivitySurface(w);

    final int nSamples = 20;
    final DoublesPair[] points = new DoublesPair[nSamples];
    for (int i = 0; i < nSamples; i++) {
      final double t = 10.0 * RANDOM.nextDouble();
      final double k = 0.15 * RANDOM.nextDouble();
      points[i] = DoublesPair.of(t, k);

      final double vol = volSurf.getVolatility(points[i]);
      final DoubleMatrix1D sense = senseSurf.getZValue(points[i].toPair());
      final Pair<Double, DoubleMatrix1D> volAndSense =
          volAndSenseSurf.getZValue(points[i].toPair());
      assertEquals(vol, volAndSense.getFirst(), 1e-15);
      AssertMatrix.assertEqualsVectors(sense, volAndSense.getSecond(), 1e-15);
    }

    // create a DiscreteVolatilityFunctionProvider in order to compute the Jacobian for a (random)
    // set the points
    final DiscreteVolatilityFunctionProvider dvfp =
        new DiscreteVolatilityFunctionProviderFromVolSurface(vsPro);
    final DiscreteVolatilityFunction func = dvfp.from(points);
    final DoubleMatrix2D jac = func.calculateJacobian(w);
    final DoubleMatrix2D jacFD = func.calculateJacobianViaFD(w);
    AssertMatrix.assertEqualsMatrix(jacFD, jac, 1e-10);
  }
  public void partitionByValue() {
    List<LocalDate> dates =
        dates(DATE_2010_01_01, DATE_2011_06_01, DATE_2012_01_01, DATE_2013_06_01, DATE_2014_01_01);
    LocalDateDoubleTimeSeries series =
        LocalDateDoubleTimeSeries.builder().putAll(dates, VALUES_10_14).build();

    Pair<LocalDateDoubleTimeSeries, LocalDateDoubleTimeSeries> partition =
        series.partitionByValue(d -> d > 10 && d < 14);

    LocalDateDoubleTimeSeries mid = partition.getFirst();
    LocalDateDoubleTimeSeries extreme = partition.getSecond();

    assertThat(mid.size()).isEqualTo(3);
    assertThat(extreme.size()).isEqualTo(2);

    assertThat(mid.get(DATE_2011_06_01)).hasValue(11);
    assertThat(mid.get(DATE_2012_01_01)).hasValue(12);
    assertThat(mid.get(DATE_2013_06_01)).hasValue(13);

    assertThat(extreme.get(DATE_2010_01_01)).hasValue(10);
    assertThat(extreme.get(DATE_2014_01_01)).hasValue(14);
  }
  public void partition() {
    List<LocalDate> dates =
        dates(DATE_2010_01_01, DATE_2011_06_01, DATE_2012_01_01, DATE_2013_06_01, DATE_2014_01_01);
    LocalDateDoubleTimeSeries series =
        LocalDateDoubleTimeSeries.builder().putAll(dates, VALUES_10_14).build();

    Pair<LocalDateDoubleTimeSeries, LocalDateDoubleTimeSeries> partition =
        series.partition((ld, d) -> ld.getYear() % 2 == 0);

    LocalDateDoubleTimeSeries even = partition.getFirst();
    LocalDateDoubleTimeSeries odd = partition.getSecond();

    assertThat(even.size()).isEqualTo(3);
    assertThat(odd.size()).isEqualTo(2);

    assertThat(even.get(DATE_2010_01_01)).hasValue(10);
    assertThat(even.get(DATE_2012_01_01)).hasValue(12);
    assertThat(even.get(DATE_2014_01_01)).hasValue(14);

    assertThat(odd.get(DATE_2011_06_01)).hasValue(11);
    assertThat(odd.get(DATE_2013_06_01)).hasValue(13);
  }
 /**
  * The value of the function at the given point and its sensitivity to the weights of the basis
  * functions.
  *
  * @param x value to be evaluated
  * @return value and weight sensitivity
  */
 public Pair<Double, DoubleArray> valueAndWeightSensitivity(T x) {
   ArgChecker.notNull(x, "x");
   int n = _w.length;
   double sum = 0;
   double[] data = new double[n];
   for (int i = 0; i < n; i++) {
     double temp = _f.get(i).apply(x);
     if (temp != 0.0) {
       sum += _w[i] * temp;
       data[i] = temp;
     }
   }
   return Pair.of(sum, DoubleArray.ofUnsafe(data));
 }
 /** Tests combining rating and sector. */
 @Test
 public void testRatingAndSector() {
   final LegalEntityCombiningFilter filter = new LegalEntityCombiningFilter();
   final Set<LegalEntityFilter<LegalEntity>> underlyingFilters = new HashSet<>();
   final LegalEntityCreditRatings ratingsFilter = new LegalEntityCreditRatings();
   ratingsFilter.setPerAgencyRatings(Collections.singleton("S&P"));
   underlyingFilters.add(ratingsFilter);
   final LegalEntitySector sectorFilter = new LegalEntitySector();
   sectorFilter.setUseSectorName(true);
   underlyingFilters.add(sectorFilter);
   underlyingFilters.add(new LegalEntityCombiningFilter());
   filter.setFiltersToUse(underlyingFilters);
   final Set<Object> expected = new HashSet<>();
   expected.add(LegalEntityTest.SECTOR.getName());
   expected.add(Pair.of("S&P", "A"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class,
           VariantType.either(
               String.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class))),
       filter.getFilteredDataType());
 }
 /** Tests ratings requests. */
 @Test
 public void testCreditRatings() {
   LegalEntityCreditRatings filter = new LegalEntityCreditRatings();
   assertEquals(
       LegalEntityTest.CREDIT_RATINGS, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(
       LegalEntityTest.CREDIT_RATINGS,
       filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   final Set<CreditRating> creditRatings = new HashSet<>(LegalEntityTest.CREDIT_RATINGS);
   creditRatings.add(CreditRating.of("C", "Poor", "Test", false));
   assertEquals(
       ParameterizedTypeImpl.of(Set.class, CreditRating.class), filter.getFilteredDataType());
   filter = new LegalEntityCreditRatings();
   filter.setUseRating(true);
   Set<Pair<String, String>> expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "B"));
   expected.add(Pair.of("S&P", "A"));
   expected.add(Pair.of("Test", "C"));
   assertEquals(
       expected,
       filter.getFilteredData(
           new LegalEntity(null, LegalEntityTest.SHORT_NAME, creditRatings, null, null)));
   assertEquals(
       expected,
       filter.getFilteredData(
           new LegalEntityWithREDCode(
               null, LegalEntityTest.SHORT_NAME, creditRatings, null, null, "")));
   expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "B"));
   expected.add(Pair.of("S&P", "A"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class)),
       filter.getFilteredDataType());
   filter = new LegalEntityCreditRatings();
   filter.setPerAgencyRatings(Collections.singleton("Moody's"));
   expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "B"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class)),
       filter.getFilteredDataType());
   filter = new LegalEntityCreditRatings();
   filter.setPerAgencyRatings(Collections.singleton("S&P"));
   expected = new HashSet<>();
   expected.add(Pair.of("S&P", "A"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class)),
       filter.getFilteredDataType());
   filter = new LegalEntityCreditRatings();
   filter.setUseRatingDescription(true);
   expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "Investment Grade"));
   expected.add(Pair.of("S&P", "Prime"));
   expected.add(Pair.of("Test", "Poor"));
   assertEquals(
       expected,
       filter.getFilteredData(
           new LegalEntity(null, LegalEntityTest.SHORT_NAME, creditRatings, null, null)));
   assertEquals(
       expected,
       filter.getFilteredData(
           new LegalEntityWithREDCode(
               null, LegalEntityTest.SHORT_NAME, creditRatings, null, null, "")));
   expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "Investment Grade"));
   expected.add(Pair.of("S&P", "Prime"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class)),
       filter.getFilteredDataType());
   filter = new LegalEntityCreditRatings();
   filter.setPerAgencyRatingDescriptions(Collections.singleton("Moody's"));
   expected = new HashSet<>();
   expected.add(Pair.of("Moody's", "Investment Grade"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   filter = new LegalEntityCreditRatings();
   filter.setPerAgencyRatingDescriptions(Collections.singleton("S&P"));
   expected = new HashSet<>();
   expected.add(Pair.of("S&P", "Prime"));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY));
   assertEquals(expected, filter.getFilteredData(LegalEntityTest.LEGAL_ENTITY_RED_CODE));
   assertEquals(
       ParameterizedTypeImpl.of(
           Set.class, ParameterizedTypeImpl.of(Pair.class, String.class, String.class)),
       filter.getFilteredDataType());
 }
  @Test(enabled = true)
  public void swapRateDx2Ddcf() {
    final double theta = 1.99;
    final double rhog2pp = MODEL_PARAMETERS.getCorrelation();
    final double[][] gamma = MODEL_G2PP.gamma(MODEL_PARAMETERS, 0, theta);
    double[][] alphaFixed = new double[T_FIXED.length][2];
    double[] tau2Fixed = new double[T_FIXED.length];
    final double[][] alphaIbor = new double[T_IBOR.length][2];
    final double[] tau2Ibor = new double[T_IBOR.length];
    final double[][] hthetaFixed =
        MODEL_G2PP.volatilityMaturityPart(MODEL_PARAMETERS, theta, T_FIXED);
    alphaFixed = new double[2][T_FIXED.length];
    tau2Fixed = new double[T_FIXED.length];
    for (int loopcf = 0; loopcf < T_FIXED.length; loopcf++) {
      alphaFixed[loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaFixed[0][loopcf];
      alphaFixed[loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaFixed[1][loopcf];
      tau2Fixed[loopcf] =
          alphaFixed[loopcf][0] * alphaFixed[loopcf][0]
              + alphaFixed[loopcf][1] * alphaFixed[loopcf][1]
              + 2 * rhog2pp * gamma[0][1] * hthetaFixed[0][loopcf] * hthetaFixed[1][loopcf];
    }
    final double[][] hthetaIbor =
        MODEL_G2PP.volatilityMaturityPart(MODEL_PARAMETERS, theta, T_IBOR);
    for (int loopcf = 0; loopcf < T_IBOR.length; loopcf++) {
      alphaIbor[loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaIbor[0][loopcf];
      alphaIbor[loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaIbor[1][loopcf];
      tau2Ibor[loopcf] =
          alphaIbor[loopcf][0] * alphaIbor[loopcf][0]
              + alphaIbor[loopcf][1] * alphaIbor[loopcf][1]
              + 2 * rhog2pp * gamma[0][1] * hthetaIbor[0][loopcf] * hthetaIbor[1][loopcf];
    }

    final double shift = 1.0E-7;
    final double[] x = {0.0, 0.1};
    final Pair<double[][][], double[][][]> dx2ddcfComputed =
        MODEL_G2PP.swapRateDdcfDx2(
            x, DCF_FIXED, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor);
    final double[][][] dx2DdcffExpected = new double[DCF_FIXED.length][2][2];
    for (int loopcf = 0; loopcf < DCF_FIXED.length; loopcf++) {
      final double[] dsf_bumped = DCF_FIXED.clone();
      dsf_bumped[loopcf] += shift;
      final double[] d1Plus = new double[2];
      final double[][] d2Plus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, dsf_bumped, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor, d1Plus, d2Plus);
      dsf_bumped[loopcf] -= 2 * shift;
      final double[] d1Minus = new double[2];
      final double[][] d2Minus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, dsf_bumped, alphaFixed, tau2Fixed, DCF_IBOR, alphaIbor, tau2Ibor, d1Minus, d2Minus);
      for (int loopd1 = 0; loopd1 < 2; loopd1++) {
        for (int loopd2 = loopd1; loopd2 < 2; loopd2++) {
          dx2DdcffExpected[loopcf][loopd1][loopd2] =
              (d2Plus[loopd1][loopd2] - d2Minus[loopd1][loopd2]) / (2 * shift);
          assertEquals(
              "Hull-White model: swap rate",
              dx2DdcffExpected[loopcf][loopd1][loopd2],
              dx2ddcfComputed.getFirst()[loopcf][loopd1][loopd2],
              TOLERANCE_RATE_DELTA2);
        }
      }
    }
    final double[][][] dx2DdcfiExpected = new double[DCF_IBOR.length][2][2];
    for (int loopcf = 0; loopcf < DCF_IBOR.length; loopcf++) {
      final double[] dsf_bumped = DCF_IBOR.clone();
      dsf_bumped[loopcf] += shift;
      final double[] d1Plus = new double[2];
      final double[][] d2Plus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, DCF_FIXED, alphaFixed, tau2Fixed, dsf_bumped, alphaIbor, tau2Ibor, d1Plus, d2Plus);
      dsf_bumped[loopcf] -= 2 * shift;
      final double[] d1Minus = new double[2];
      final double[][] d2Minus = new double[2][2];
      MODEL_G2PP.swapRate(
          x, DCF_FIXED, alphaFixed, tau2Fixed, dsf_bumped, alphaIbor, tau2Ibor, d1Minus, d2Minus);
      for (int loopd1 = 0; loopd1 < 2; loopd1++) {
        for (int loopd2 = loopd1; loopd2 < 2; loopd2++) {
          dx2DdcfiExpected[loopcf][loopd1][loopd2] =
              (d2Plus[loopd1][loopd2] - d2Minus[loopd1][loopd2]) / (2 * shift);
          assertEquals(
              "Hull-White model: swap rate",
              dx2DdcfiExpected[loopcf][loopd1][loopd2],
              dx2ddcfComputed.getSecond()[loopcf][loopd1][loopd2],
              TOLERANCE_RATE_DELTA2);
        }
      }
    }
  }
/** Test */
@Test
public class DiscountingFixedCouponBondProductPricerTest {
  // fixed coupon bond
  private static final StandardId SECURITY_ID = StandardId.of("OG-Ticker", "GOVT1-BOND1");
  private static final StandardId ISSUER_ID = StandardId.of("OG-Ticker", "GOVT1");
  private static final LocalDate VAL_DATE = date(2016, 4, 25);
  private static final YieldConvention YIELD_CONVENTION = YieldConvention.GERMAN_BONDS;
  private static final double NOTIONAL = 1.0e7;
  private static final double FIXED_RATE = 0.015;
  private static final HolidayCalendar EUR_CALENDAR = HolidayCalendars.EUTA;
  private static final DaysAdjustment DATE_OFFSET = DaysAdjustment.ofBusinessDays(3, EUR_CALENDAR);
  private static final DayCount DAY_COUNT = DayCounts.ACT_365F;
  private static final LocalDate START_DATE = LocalDate.of(2015, 4, 12);
  private static final LocalDate END_DATE = LocalDate.of(2025, 4, 12);
  private static final BusinessDayAdjustment BUSINESS_ADJUST =
      BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, EUR_CALENDAR);
  private static final PeriodicSchedule PERIOD_SCHEDULE =
      PeriodicSchedule.of(
          START_DATE,
          END_DATE,
          Frequency.P6M,
          BUSINESS_ADJUST,
          StubConvention.SHORT_INITIAL,
          false);
  private static final DaysAdjustment EX_COUPON =
      DaysAdjustment.ofBusinessDays(-5, EUR_CALENDAR, BUSINESS_ADJUST);
  /** nonzero ex-coupon period */
  private static final FixedCouponBond PRODUCT =
      FixedCouponBond.builder()
          .dayCount(DAY_COUNT)
          .fixedRate(FIXED_RATE)
          .legalEntityId(ISSUER_ID)
          .currency(EUR)
          .notional(NOTIONAL)
          .periodicSchedule(PERIOD_SCHEDULE)
          .settlementDateOffset(DATE_OFFSET)
          .yieldConvention(YIELD_CONVENTION)
          .exCouponPeriod(EX_COUPON)
          .build();

  private static final Security<FixedCouponBond> BOND_SECURITY =
      UnitSecurity.builder(PRODUCT).standardId(SECURITY_ID).build();
  /** no ex-coupon period */
  private static final FixedCouponBond PRODUCT_NO_EXCOUPON =
      FixedCouponBond.builder()
          .dayCount(DAY_COUNT)
          .fixedRate(FIXED_RATE)
          .legalEntityId(ISSUER_ID)
          .currency(EUR)
          .notional(NOTIONAL)
          .periodicSchedule(PERIOD_SCHEDULE)
          .settlementDateOffset(DATE_OFFSET)
          .yieldConvention(YIELD_CONVENTION)
          .build();

  // rates provider
  private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
  private static final CurveName NAME_REPO = CurveName.of("TestRepoCurve");
  private static final CurveMetadata METADATA_REPO = Curves.zeroRates(NAME_REPO, ACT_365F);
  private static final InterpolatedNodalCurve CURVE_REPO =
      InterpolatedNodalCurve.of(
          METADATA_REPO,
          DoubleArray.of(0.1, 2.0, 10.0),
          DoubleArray.of(0.05, 0.06, 0.09),
          INTERPOLATOR);
  private static final DiscountFactors DSC_FACTORS_REPO =
      ZeroRateDiscountFactors.of(EUR, VAL_DATE, CURVE_REPO);
  private static final BondGroup GROUP_REPO = BondGroup.of("GOVT1 BOND1");
  private static final CurveName NAME_ISSUER = CurveName.of("TestIssuerCurve");
  private static final CurveMetadata METADATA_ISSUER = Curves.zeroRates(NAME_ISSUER, ACT_365F);
  private static final InterpolatedNodalCurve CURVE_ISSUER =
      InterpolatedNodalCurve.of(
          METADATA_ISSUER,
          DoubleArray.of(0.2, 9.0, 15.0),
          DoubleArray.of(0.03, 0.05, 0.13),
          INTERPOLATOR);
  private static final DiscountFactors DSC_FACTORS_ISSUER =
      ZeroRateDiscountFactors.of(EUR, VAL_DATE, CURVE_ISSUER);
  private static final LegalEntityGroup GROUP_ISSUER = LegalEntityGroup.of("GOVT1");
  private static final LegalEntityDiscountingProvider PROVIDER =
      LegalEntityDiscountingProvider.builder()
          .issuerCurves(
              ImmutableMap.<Pair<LegalEntityGroup, Currency>, DiscountFactors>of(
                  Pair.<LegalEntityGroup, Currency>of(GROUP_ISSUER, EUR), DSC_FACTORS_ISSUER))
          .legalEntityMap(ImmutableMap.<StandardId, LegalEntityGroup>of(ISSUER_ID, GROUP_ISSUER))
          .repoCurves(
              ImmutableMap.<Pair<BondGroup, Currency>, DiscountFactors>of(
                  Pair.<BondGroup, Currency>of(GROUP_REPO, EUR), DSC_FACTORS_REPO))
          .bondMap(ImmutableMap.<StandardId, BondGroup>of(SECURITY_ID, GROUP_REPO))
          .valuationDate(VAL_DATE)
          .build();

  private static final double Z_SPREAD = 0.035;
  private static final int PERIOD_PER_YEAR = 4;
  private static final double TOL = 1.0e-12;
  private static final double EPS = 1.0e-6;

  // pricers
  private static final DiscountingFixedCouponBondProductPricer PRICER =
      DiscountingFixedCouponBondProductPricer.DEFAULT;
  private static final DiscountingPaymentPricer PRICER_NOMINAL = DiscountingPaymentPricer.DEFAULT;
  private static final DiscountingFixedCouponBondPaymentPeriodPricer PRICER_COUPON =
      DiscountingFixedCouponBondPaymentPeriodPricer.DEFAULT;
  private static final RatesFiniteDifferenceSensitivityCalculator FD_CAL =
      new RatesFiniteDifferenceSensitivityCalculator(EPS);

  // -------------------------------------------------------------------------
  public void test_presentValue() {
    CurrencyAmount computed = PRICER.presentValue(PRODUCT, PROVIDER);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(expanded.getNominalPayment(), DSC_FACTORS_ISSUER);
    int size = expanded.getPeriodicPayments().size();
    double pvCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvCupon +=
          PRICER_COUPON.presentValue(
              payment, IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER));
    }
    expected = expected.plus(pvCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  public void test_presentValueWithZSpread_continuous() {
    CurrencyAmount computed =
        PRICER.presentValueWithZSpread(PRODUCT, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(
            expanded.getNominalPayment(), DSC_FACTORS_ISSUER, Z_SPREAD, CONTINUOUS, 0);
    int size = expanded.getPeriodicPayments().size();
    double pvcCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvcCupon +=
          PRICER_COUPON.presentValueWithSpread(
              payment,
              IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER),
              Z_SPREAD,
              CONTINUOUS,
              0);
    }
    expected = expected.plus(pvcCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  public void test_presentValueWithZSpread_periodic() {
    CurrencyAmount computed =
        PRICER.presentValueWithZSpread(PRODUCT, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(
            expanded.getNominalPayment(), DSC_FACTORS_ISSUER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    int size = expanded.getPeriodicPayments().size();
    double pvcCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvcCupon +=
          PRICER_COUPON.presentValueWithSpread(
              payment,
              IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER),
              Z_SPREAD,
              PERIODIC,
              PERIOD_PER_YEAR);
    }
    expected = expected.plus(pvcCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  public void test_presentValue_noExcoupon() {
    CurrencyAmount computed = PRICER.presentValue(PRODUCT_NO_EXCOUPON, PROVIDER);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(expanded.getNominalPayment(), DSC_FACTORS_ISSUER);
    int size = expanded.getPeriodicPayments().size();
    double pvcCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvcCupon +=
          PRICER_COUPON.presentValue(
              payment, IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER));
    }
    expected = expected.plus(pvcCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  public void test_presentValueWithZSpread_continuous_noExcoupon() {
    CurrencyAmount computed =
        PRICER.presentValueWithZSpread(PRODUCT_NO_EXCOUPON, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(
            expanded.getNominalPayment(), DSC_FACTORS_ISSUER, Z_SPREAD, CONTINUOUS, 0);
    int size = expanded.getPeriodicPayments().size();
    double pvcCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvcCupon +=
          PRICER_COUPON.presentValueWithSpread(
              payment,
              IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER),
              Z_SPREAD,
              CONTINUOUS,
              0);
    }
    expected = expected.plus(pvcCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  public void test_presentValueWithZSpread_periodic_noExcoupon() {
    CurrencyAmount computed =
        PRICER.presentValueWithZSpread(
            PRODUCT_NO_EXCOUPON, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    ExpandedFixedCouponBond expanded = PRODUCT.expand();
    CurrencyAmount expected =
        PRICER_NOMINAL.presentValue(
            expanded.getNominalPayment(), DSC_FACTORS_ISSUER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    int size = expanded.getPeriodicPayments().size();
    double pvcCupon = 0d;
    for (int i = 2; i < size; ++i) {
      FixedCouponBondPaymentPeriod payment = expanded.getPeriodicPayments().get(i);
      pvcCupon +=
          PRICER_COUPON.presentValueWithSpread(
              payment,
              IssuerCurveDiscountFactors.of(DSC_FACTORS_ISSUER, GROUP_ISSUER),
              Z_SPREAD,
              PERIODIC,
              PERIOD_PER_YEAR);
    }
    expected = expected.plus(pvcCupon);
    assertEquals(computed.getCurrency(), EUR);
    assertEquals(computed.getAmount(), expected.getAmount(), NOTIONAL * TOL);
  }

  // -------------------------------------------------------------------------
  public void test_dirtyPriceFromCurves() {
    double computed = PRICER.dirtyPriceFromCurves(BOND_SECURITY, PROVIDER);
    CurrencyAmount pv = PRICER.presentValue(PRODUCT, PROVIDER);
    LocalDate settlement = DATE_OFFSET.adjust(VAL_DATE);
    double df = DSC_FACTORS_REPO.discountFactor(settlement);
    assertEquals(computed, pv.getAmount() / df / NOTIONAL);
  }

  public void test_dirtyPriceFromCurvesWithZSpread_continuous() {
    double computed =
        PRICER.dirtyPriceFromCurvesWithZSpread(BOND_SECURITY, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    CurrencyAmount pv = PRICER.presentValueWithZSpread(PRODUCT, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    LocalDate settlement = DATE_OFFSET.adjust(VAL_DATE);
    double df = DSC_FACTORS_REPO.discountFactor(settlement);
    assertEquals(computed, pv.getAmount() / df / NOTIONAL);
  }

  public void test_dirtyPriceFromCurvesWithZSpread_periodic() {
    double computed =
        PRICER.dirtyPriceFromCurvesWithZSpread(
            BOND_SECURITY, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    CurrencyAmount pv =
        PRICER.presentValueWithZSpread(PRODUCT, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    LocalDate settlement = DATE_OFFSET.adjust(VAL_DATE);
    double df = DSC_FACTORS_REPO.discountFactor(settlement);
    assertEquals(computed, pv.getAmount() / df / NOTIONAL);
  }

  public void test_dirtyPriceFromCleanPrice_cleanPriceFromDirtyPrice() {
    double dirtyPrice = PRICER.dirtyPriceFromCurves(BOND_SECURITY, PROVIDER);
    LocalDate settlement = DATE_OFFSET.adjust(VAL_DATE);
    double cleanPrice = PRICER.cleanPriceFromDirtyPrice(PRODUCT, settlement, dirtyPrice);
    double accruedInterest = PRICER.accruedInterest(PRODUCT, settlement);
    assertEquals(cleanPrice, dirtyPrice - accruedInterest / NOTIONAL, NOTIONAL * TOL);
    double dirtyPriceRe = PRICER.dirtyPriceFromCleanPrice(PRODUCT, settlement, cleanPrice);
    assertEquals(dirtyPriceRe, dirtyPrice, TOL);
  }

  // -------------------------------------------------------------------------
  public void test_zSpreadFromCurvesAndPV_continuous() {
    double dirtyPrice =
        PRICER.dirtyPriceFromCurvesWithZSpread(BOND_SECURITY, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    double computed =
        PRICER.zSpreadFromCurvesAndDirtyPrice(BOND_SECURITY, PROVIDER, dirtyPrice, CONTINUOUS, 0);
    assertEquals(computed, Z_SPREAD, TOL);
  }

  public void test_zSpreadFromCurvesAndPV_periodic() {
    double dirtyPrice =
        PRICER.dirtyPriceFromCurvesWithZSpread(
            BOND_SECURITY, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    double computed =
        PRICER.zSpreadFromCurvesAndDirtyPrice(
            BOND_SECURITY, PROVIDER, dirtyPrice, PERIODIC, PERIOD_PER_YEAR);
    assertEquals(computed, Z_SPREAD, TOL);
  }

  // -------------------------------------------------------------------------
  public void test_presentValueSensitivity() {
    PointSensitivityBuilder point = PRICER.presentValueSensitivity(PRODUCT, PROVIDER);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(PROVIDER, (p) -> PRICER.presentValue(PRODUCT, (p)));
    assertTrue(computed.equalWithTolerance(expected, 30d * NOTIONAL * EPS));
  }

  public void test_presentValueSensitivityWithZSpread_continuous() {
    PointSensitivityBuilder point =
        PRICER.presentValueSensitivityWithZSpread(PRODUCT, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER, (p) -> PRICER.presentValueWithZSpread(PRODUCT, (p), Z_SPREAD, CONTINUOUS, 0));
    assertTrue(computed.equalWithTolerance(expected, 20d * NOTIONAL * EPS));
  }

  public void test_presentValueSensitivityWithZSpread_periodic() {
    PointSensitivityBuilder point =
        PRICER.presentValueSensitivityWithZSpread(
            PRODUCT, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) ->
                PRICER.presentValueWithZSpread(PRODUCT, (p), Z_SPREAD, PERIODIC, PERIOD_PER_YEAR));
    assertTrue(computed.equalWithTolerance(expected, 20d * NOTIONAL * EPS));
  }

  public void test_presentValueProductSensitivity_noExcoupon() {
    PointSensitivityBuilder point = PRICER.presentValueSensitivity(PRODUCT_NO_EXCOUPON, PROVIDER);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(PROVIDER, (p) -> PRICER.presentValue(PRODUCT_NO_EXCOUPON, (p)));
    assertTrue(computed.equalWithTolerance(expected, 30d * NOTIONAL * EPS));
  }

  public void test_presentValueSensitivityWithZSpread_continuous_noExcoupon() {
    PointSensitivityBuilder point =
        PRICER.presentValueSensitivityWithZSpread(
            PRODUCT_NO_EXCOUPON, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) ->
                PRICER.presentValueWithZSpread(PRODUCT_NO_EXCOUPON, (p), Z_SPREAD, CONTINUOUS, 0));
    assertTrue(computed.equalWithTolerance(expected, 20d * NOTIONAL * EPS));
  }

  public void test_presentValueSensitivityWithZSpread_periodic_noExcoupon() {
    PointSensitivityBuilder point =
        PRICER.presentValueSensitivityWithZSpread(
            PRODUCT_NO_EXCOUPON, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) ->
                PRICER.presentValueWithZSpread(
                    PRODUCT_NO_EXCOUPON, (p), Z_SPREAD, PERIODIC, PERIOD_PER_YEAR));
    assertTrue(computed.equalWithTolerance(expected, 20d * NOTIONAL * EPS));
  }

  public void test_dirtyPriceSensitivity() {
    PointSensitivityBuilder point = PRICER.dirtyPriceSensitivity(BOND_SECURITY, PROVIDER);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) -> CurrencyAmount.of(EUR, PRICER.dirtyPriceFromCurves(BOND_SECURITY, (p))));
    assertTrue(computed.equalWithTolerance(expected, NOTIONAL * EPS));
  }

  public void test_dirtyPriceSensitivityWithZspread_continuous() {
    PointSensitivityBuilder point =
        PRICER.dirtyPriceSensitivityWithZspread(BOND_SECURITY, PROVIDER, Z_SPREAD, CONTINUOUS, 0);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) ->
                CurrencyAmount.of(
                    EUR,
                    PRICER.dirtyPriceFromCurvesWithZSpread(
                        BOND_SECURITY, (p), Z_SPREAD, CONTINUOUS, 0)));
    assertTrue(computed.equalWithTolerance(expected, NOTIONAL * EPS));
  }

  public void test_dirtyPriceSensitivityWithZspread_periodic() {
    PointSensitivityBuilder point =
        PRICER.dirtyPriceSensitivityWithZspread(
            BOND_SECURITY, PROVIDER, Z_SPREAD, PERIODIC, PERIOD_PER_YEAR);
    CurveCurrencyParameterSensitivities computed =
        PROVIDER.curveParameterSensitivity(point.build());
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            PROVIDER,
            (p) ->
                CurrencyAmount.of(
                    EUR,
                    PRICER.dirtyPriceFromCurvesWithZSpread(
                        BOND_SECURITY, (p), Z_SPREAD, PERIODIC, PERIOD_PER_YEAR)));
    assertTrue(computed.equalWithTolerance(expected, NOTIONAL * EPS));
  }

  // -------------------------------------------------------------------------
  public void test_accruedInterest() {
    // settle before start
    LocalDate settleDate1 = START_DATE.minusDays(5);
    double accruedInterest1 = PRICER.accruedInterest(PRODUCT, settleDate1);
    assertEquals(accruedInterest1, 0d);
    // settle between endDate and endDate -lag
    LocalDate settleDate2 = date(2015, 10, 8);
    double accruedInterest2 = PRICER.accruedInterest(PRODUCT, settleDate2);
    assertEquals(accruedInterest2, -4.0 / 365.0 * FIXED_RATE * NOTIONAL, EPS);
    // normal
    LocalDate settleDate3 = date(2015, 4, 18); // not adjusted
    FixedCouponBond product =
        FixedCouponBond.builder()
            .dayCount(DAY_COUNT)
            .fixedRate(FIXED_RATE)
            .legalEntityId(ISSUER_ID)
            .currency(EUR)
            .notional(NOTIONAL)
            .periodicSchedule(PERIOD_SCHEDULE)
            .settlementDateOffset(DATE_OFFSET)
            .yieldConvention(YIELD_CONVENTION)
            .exCouponPeriod(DaysAdjustment.NONE)
            .build();
    double accruedInterest3 = PRICER.accruedInterest(product, settleDate3);
    assertEquals(accruedInterest3, 6.0 / 365.0 * FIXED_RATE * NOTIONAL, EPS);
  }

  // -------------------------------------------------------------------------
  /* US Street convention */
  private static final LocalDate START_US = date(2006, 11, 15);
  private static final LocalDate END_US = START_US.plusYears(10);
  private static final PeriodicSchedule SCHEDULE_US =
      PeriodicSchedule.of(
          START_US,
          END_US,
          Frequency.P6M,
          BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, HolidayCalendars.SAT_SUN),
          StubConvention.SHORT_INITIAL,
          false);
  private static final FixedCouponBond PRODUCT_US =
      FixedCouponBond.builder()
          .dayCount(DayCounts.ACT_ACT_ICMA)
          .fixedRate(0.04625)
          .legalEntityId(ISSUER_ID)
          .currency(Currency.USD)
          .notional(100)
          .periodicSchedule(SCHEDULE_US)
          .settlementDateOffset(DaysAdjustment.ofBusinessDays(3, HolidayCalendars.SAT_SUN))
          .yieldConvention(YieldConvention.US_STREET)
          .exCouponPeriod(DaysAdjustment.NONE)
          .build();
  private static final LocalDate VALUATION_US = date(2011, 8, 18);
  private static final LocalDate SETTLEMENT_US =
      PRODUCT_US.getSettlementDateOffset().adjust(VALUATION_US);
  private static final LocalDate VALUATION_LAST_US = date(2016, 6, 3);
  private static final LocalDate SETTLEMENT_LAST_US =
      PRODUCT_US.getSettlementDateOffset().adjust(VALUATION_LAST_US);
  private static final double YIELD_US = 0.04;

  public void dirtyPriceFromYieldUS() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    assertEquals(dirtyPrice, 1.0417352500524246, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_US, SETTLEMENT_US, dirtyPrice);
    assertEquals(yield, YIELD_US, TOL);
  }

  public void dirtyPriceFromYieldUSLastPeriod() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    assertEquals(dirtyPrice, 1.005635683760684, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_US, SETTLEMENT_LAST_US, dirtyPrice);
    assertEquals(yield, YIELD_US, TOL);
  }

  public void modifiedDurationFromYieldUS() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void modifiedDurationFromYieldUSLastPeriod() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldUS() {
    double computed = PRICER.convexityFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    double durationUp = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US + EPS);
    double durationDw = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldUSLastPeriod() {
    double computed = PRICER.convexityFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    double durationUp =
        PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US + EPS);
    double durationDw =
        PRICER.modifiedDurationFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void macaulayDurationFromYieldUS() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_US, SETTLEMENT_US, YIELD_US);
    assertEquals(duration, 4.6575232098896215, TOL); // 2.x.
  }

  public void macaulayDurationFromYieldUSLastPeriod() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_US, SETTLEMENT_LAST_US, YIELD_US);
    assertEquals(duration, 0.43478260869565216, TOL); // 2.x.
  }

  /* UK BUMP/DMO convention */
  private static final LocalDate START_UK = date(2002, 9, 7);
  private static final LocalDate END_UK = START_UK.plusYears(12);
  private static final PeriodicSchedule SCHEDULE_UK =
      PeriodicSchedule.of(
          START_UK,
          END_UK,
          Frequency.P6M,
          BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, HolidayCalendars.SAT_SUN),
          StubConvention.SHORT_INITIAL,
          false);
  private static final FixedCouponBond PRODUCT_UK =
      FixedCouponBond.builder()
          .dayCount(DayCounts.ACT_ACT_ICMA)
          .fixedRate(0.05)
          .legalEntityId(ISSUER_ID)
          .currency(Currency.GBP)
          .notional(100)
          .periodicSchedule(SCHEDULE_UK)
          .settlementDateOffset(DaysAdjustment.ofBusinessDays(1, HolidayCalendars.SAT_SUN))
          .yieldConvention(YieldConvention.UK_BUMP_DMO)
          .exCouponPeriod(
              DaysAdjustment.ofCalendarDays(
                  -7,
                  BusinessDayAdjustment.of(
                      BusinessDayConventions.PRECEDING, HolidayCalendars.SAT_SUN)))
          .build();
  private static final LocalDate VALUATION_UK = date(2011, 9, 2);
  private static final LocalDate SETTLEMENT_UK =
      PRODUCT_UK.getSettlementDateOffset().adjust(VALUATION_UK);
  private static final LocalDate VALUATION_LAST_UK = date(2014, 6, 3);
  private static final LocalDate SETTLEMENT_LAST_UK =
      PRODUCT_UK.getSettlementDateOffset().adjust(VALUATION_LAST_UK);
  private static final double YIELD_UK = 0.04;

  public void dirtyPriceFromYieldUK() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    assertEquals(dirtyPrice, 1.0277859038905428, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_UK, SETTLEMENT_UK, dirtyPrice);
    assertEquals(yield, YIELD_UK, TOL);
  }

  public void dirtyPriceFromYieldUKLastPeriod() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    assertEquals(dirtyPrice, 1.0145736043763598, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_UK, SETTLEMENT_LAST_UK, dirtyPrice);
    assertEquals(yield, YIELD_UK, TOL);
  }

  public void modifiedDurationFromYieldUK() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void modifiedDurationFromYieldUKLastPeriod() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldUK() {
    double computed = PRICER.convexityFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    double durationUp = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK + EPS);
    double durationDw = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldUKLastPeriod() {
    double computed = PRICER.convexityFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    double durationUp =
        PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK + EPS);
    double durationDw =
        PRICER.modifiedDurationFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void macaulayDurationFromYieldUK() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_UK, SETTLEMENT_UK, YIELD_UK);
    assertEquals(duration, 2.8312260658609163, TOL); // 2.x.
  }

  public void macaulayDurationFromYieldUKLastPeriod() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_UK, SETTLEMENT_LAST_UK, YIELD_UK);
    assertEquals(duration, 0.25815217391304346, TOL); // 2.x.
  }

  /* German bond convention */
  private static final LocalDate START_GER = date(2002, 9, 7);
  private static final LocalDate END_GER = START_GER.plusYears(12);
  private static final PeriodicSchedule SCHEDULE_GER =
      PeriodicSchedule.of(
          START_GER,
          END_GER,
          Frequency.P12M,
          BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, HolidayCalendars.SAT_SUN),
          StubConvention.SHORT_INITIAL,
          false);
  private static final FixedCouponBond PRODUCT_GER =
      FixedCouponBond.builder()
          .dayCount(DayCounts.ACT_ACT_ICMA)
          .fixedRate(0.05)
          .legalEntityId(ISSUER_ID)
          .currency(Currency.EUR)
          .notional(100)
          .periodicSchedule(SCHEDULE_GER)
          .settlementDateOffset(DaysAdjustment.ofBusinessDays(3, HolidayCalendars.SAT_SUN))
          .yieldConvention(YieldConvention.GERMAN_BONDS)
          .exCouponPeriod(DaysAdjustment.NONE)
          .build();
  private static final LocalDate VALUATION_GER = date(2011, 9, 2);
  private static final LocalDate SETTLEMENT_GER =
      PRODUCT_GER.getSettlementDateOffset().adjust(VALUATION_GER);
  private static final LocalDate VALUATION_LAST_GER = date(2014, 6, 3);
  private static final LocalDate SETTLEMENT_LAST_GER =
      PRODUCT_GER.getSettlementDateOffset().adjust(VALUATION_LAST_GER);
  private static final double YIELD_GER = 0.04;

  public void dirtyPriceFromYieldGerman() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    assertEquals(dirtyPrice, 1.027750910332271, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_GER, SETTLEMENT_GER, dirtyPrice);
    assertEquals(yield, YIELD_GER, TOL);
  }

  public void dirtyPriceFromYieldGermanLastPeriod() {
    double dirtyPrice = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    assertEquals(dirtyPrice, 1.039406595790844, TOL); // 2.x.
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_GER, SETTLEMENT_LAST_GER, dirtyPrice);
    assertEquals(yield, YIELD_GER, TOL);
  }

  public void modifiedDurationFromYieldGER() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void modifiedDurationFromYieldGERLastPeriod() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldGER() {
    double computed = PRICER.convexityFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    double durationUp =
        PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER + EPS);
    double durationDw =
        PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldGERLastPeriod() {
    double computed = PRICER.convexityFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    double durationUp =
        PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER + EPS);
    double durationDw =
        PRICER.modifiedDurationFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void macaulayDurationFromYieldGER() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_GER, SETTLEMENT_GER, YIELD_GER);
    assertEquals(duration, 2.861462874541554, TOL); // 2.x.
  }

  public void macaulayDurationFromYieldGERLastPeriod() {
    double duration = PRICER.macaulayDurationFromYield(PRODUCT_GER, SETTLEMENT_LAST_GER, YIELD_GER);
    assertEquals(duration, 0.26231286613148186, TOL); // 2.x.
  }

  /* Japan simple convention */
  private static final LocalDate START_JP = date(2015, 9, 20);
  private static final LocalDate END_JP = START_JP.plusYears(10);
  private static final PeriodicSchedule SCHEDULE_JP =
      PeriodicSchedule.of(
          START_JP,
          END_JP,
          Frequency.P6M,
          BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, HolidayCalendars.JPTO),
          StubConvention.SHORT_INITIAL,
          false);
  private static final double RATE_JP = 0.004;
  private static final FixedCouponBond PRODUCT_JP =
      FixedCouponBond.builder()
          .dayCount(DayCounts.NL_365)
          .fixedRate(RATE_JP)
          .legalEntityId(ISSUER_ID)
          .currency(Currency.JPY)
          .notional(100)
          .periodicSchedule(SCHEDULE_JP)
          .settlementDateOffset(DaysAdjustment.ofBusinessDays(3, HolidayCalendars.JPTO))
          .yieldConvention(YieldConvention.JAPAN_SIMPLE)
          .exCouponPeriod(DaysAdjustment.NONE)
          .build();
  private static final LocalDate VALUATION_JP = date(2015, 9, 24);
  private static final LocalDate SETTLEMENT_JP =
      PRODUCT_JP.getSettlementDateOffset().adjust(VALUATION_JP);
  private static final LocalDate VALUATION_LAST_JP = date(2025, 6, 3);
  private static final LocalDate SETTLEMENT_LAST_JP =
      PRODUCT_JP.getSettlementDateOffset().adjust(VALUATION_LAST_JP);
  private static final LocalDate VALUATION_ENDED_JP = date(2026, 8, 3);
  private static final LocalDate SETTLEMENT_ENDED_JP =
      PRODUCT_JP.getSettlementDateOffset().adjust(VALUATION_ENDED_JP);
  private static final double YIELD_JP = 0.00321;

  public void dirtyPriceFromYieldJP() {
    double computed = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP);
    double maturity = DayCounts.NL_365.relativeYearFraction(SETTLEMENT_JP, END_JP);
    double expected =
        PRICER.dirtyPriceFromCleanPrice(
            PRODUCT_JP, SETTLEMENT_JP, (1d + RATE_JP * maturity) / (1d + YIELD_JP * maturity));
    assertEquals(computed, expected, TOL);
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_JP, SETTLEMENT_JP, computed);
    assertEquals(yield, YIELD_JP, TOL);
  }

  public void dirtyPriceFromYieldJPLastPeriod() {
    double computed = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP);
    double maturity = DayCounts.NL_365.relativeYearFraction(SETTLEMENT_LAST_JP, END_JP);
    double expected =
        PRICER.dirtyPriceFromCleanPrice(
            PRODUCT_JP, SETTLEMENT_LAST_JP, (1d + RATE_JP * maturity) / (1d + YIELD_JP * maturity));
    assertEquals(computed, expected, TOL);
    double yield = PRICER.yieldFromDirtyPrice(PRODUCT_JP, SETTLEMENT_LAST_JP, computed);
    assertEquals(yield, YIELD_JP, TOL);
  }

  public void dirtyPriceFromYieldJPEnded() {
    double computed = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_ENDED_JP, YIELD_JP);
    assertEquals(computed, 0d, TOL);
  }

  public void modifiedDurationFromYielddJP() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void modifiedDurationFromYieldJPLastPeriod() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP);
    double price = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP);
    double priceUp = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP + EPS);
    double priceDw = PRICER.dirtyPriceFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP - EPS);
    double expected = 0.5 * (priceDw - priceUp) / price / EPS;
    assertEquals(computed, expected, EPS);
  }

  public void modifiedDurationFromYielddJPEnded() {
    double computed = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_ENDED_JP, YIELD_JP);
    assertEquals(computed, 0d, EPS);
  }

  public void convexityFromYieldJP() {
    double computed = PRICER.convexityFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP);
    double durationUp = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP + EPS);
    double durationDw = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldJPLastPeriod() {
    double computed = PRICER.convexityFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP);
    double duration = PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP);
    double durationUp =
        PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP + EPS);
    double durationDw =
        PRICER.modifiedDurationFromYield(PRODUCT_JP, SETTLEMENT_LAST_JP, YIELD_JP - EPS);
    double expected = 0.5 * (durationDw - durationUp) / EPS + duration * duration;
    assertEquals(computed, expected, EPS);
  }

  public void convexityFromYieldJPEnded() {
    double computed = PRICER.convexityFromYield(PRODUCT_JP, SETTLEMENT_ENDED_JP, YIELD_JP);
    assertEquals(computed, 0d, EPS);
  }

  public void macaulayDurationFromYieldYieldJP() {
    assertThrows(
        () -> PRICER.macaulayDurationFromYield(PRODUCT_JP, SETTLEMENT_JP, YIELD_JP),
        UnsupportedOperationException.class,
        "The convention JAPAN_SIMPLE is not supported.");
  }
}