public void test_presentValue() {
    Currency ccy1 = TRADE.getProduct().getNonDeliverableCurrency();
    Currency ccy2 = TRADE.getProduct().getSettlementCurrency();
    LocalDate valDate = TRADE.getProduct().getPaymentDate().plusDays(7);

    FunctionConfig<FxNdfTrade> config =
        FxNdfFunctionGroups.discounting().functionConfig(TRADE, Measure.PRESENT_VALUE).get();
    CalculationSingleFunction<FxNdfTrade, ?> function = config.createFunction();
    FunctionRequirements reqs = function.requirements(TRADE);
    assertThat(reqs.getOutputCurrencies()).containsOnly(ccy1, ccy2);
    assertThat(reqs.getSingleValueRequirements())
        .isEqualTo(ImmutableSet.of(DiscountCurveKey.of(ccy1), DiscountCurveKey.of(ccy2)));
    assertThat(reqs.getTimeSeriesRequirements()).isEqualTo(ImmutableSet.of());
    assertThat(function.defaultReportingCurrency(TRADE)).hasValue(GBP);
    DiscountFactors df1 =
        SimpleDiscountFactors.of(
            ccy1, valDate, ConstantNodalCurve.of(Curves.discountFactors("Test", ACT_360), 0.99));
    DiscountFactors df2 =
        SimpleDiscountFactors.of(
            ccy2, valDate, ConstantNodalCurve.of(Curves.discountFactors("Test", ACT_360), 0.99));
    TestMarketDataMap md =
        new TestMarketDataMap(
            valDate,
            ImmutableMap.of(DiscountCurveKey.of(ccy1), df1, DiscountCurveKey.of(ccy2), df2),
            ImmutableMap.of());
    assertThat(function.execute(TRADE, md))
        .isEqualTo(FxConvertibleList.of(ImmutableList.of(CurrencyAmount.zero(GBP))));
  }
 static {
   CurveInterpolator interp = CurveInterpolators.DOUBLE_QUADRATIC;
   DoubleArray time_gbp = DoubleArray.of(0.0, 0.1, 0.25, 0.5, 0.75, 1.0, 2.0);
   DoubleArray rate_gbp = DoubleArray.of(0.0160, 0.0165, 0.0155, 0.0155, 0.0155, 0.0150, 0.014);
   InterpolatedNodalCurve dscCurve =
       InterpolatedNodalCurve.of(
           Curves.zeroRates("GBP-Discount", DAY_COUNT), time_gbp, rate_gbp, interp);
   DoubleArray time_index = DoubleArray.of(0.0, 0.25, 0.5, 1.0);
   DoubleArray rate_index = DoubleArray.of(0.0180, 0.0180, 0.0175, 0.0165);
   InterpolatedNodalCurve indexCurve =
       InterpolatedNodalCurve.of(
           Curves.zeroRates("GBP-GBPIBOR3M", DAY_COUNT), time_index, rate_index, interp);
   IMM_PROV =
       ImmutableRatesProvider.builder()
           .valuationDate(VAL_DATE)
           .discountCurves(ImmutableMap.of(GBP, dscCurve))
           .indexCurves(ImmutableMap.of(GBP_LIBOR_3M, indexCurve))
           .build();
 }
  public void coverage_functions() {
    Currency ccy1 = TRADE.getProduct().getNonDeliverableCurrency();
    Currency ccy2 = TRADE.getProduct().getSettlementCurrency();
    LocalDate valDate = TRADE.getProduct().getPaymentDate().plusDays(7);
    Curve df = ConstantNodalCurve.of(Curves.discountFactors("Test", ACT_360), 0.99);
    FxRate fxRate = FxRate.of(ccy1, ccy2, 1.6d);
    TestMarketDataMap md =
        new TestMarketDataMap(
            valDate,
            ImmutableMap.of(
                DiscountCurveKey.of(ccy1), df,
                DiscountCurveKey.of(ccy2), df,
                FxRateKey.of(ccy1, ccy2), fxRate),
            ImmutableMap.of(
                IndexRateKey.of(GBP_USD_WM),
                LocalDateDoubleTimeSeries.of(date(2015, 3, 17), 1.45d)));

    assertNotNull(new FxNdfBucketedPv01Function().execute(TRADE, md));
    assertNotNull(new FxNdfCurrencyExposureFunction().execute(TRADE, md));
    assertNotNull(new FxNdfForwardFxRateFunction().execute(TRADE, md));
    assertNotNull(new FxNdfPv01Function().execute(TRADE, md));
    assertNotNull(new FxNdfPvFunction().execute(TRADE, md));
  }
  /** Test parameter sensitivity with finite difference sensitivity calculator. No cutoff period. */
  public void rateChfNoCutOffParameterSensitivity() {
    LocalDate[] valuationDate = {date(2015, 1, 1), date(2015, 1, 8)};
    double[] time = new double[] {0.0, 0.5, 1.0, 2.0, 5.0, 10.0};
    double[] rate = new double[] {0.0100, 0.0110, 0.0115, 0.0130, 0.0135, 0.0135};

    for (int loopvaldate = 0; loopvaldate < 2; loopvaldate++) {
      Curve onCurve =
          InterpolatedNodalCurve.of(Curves.zeroRates("ON", ACT_ACT_ISDA), time, rate, INTERPOLATOR);
      ImmutableRatesProvider prov =
          ImmutableRatesProvider.builder()
              .valuationDate(valuationDate[loopvaldate])
              .indexCurves(ImmutableMap.of(CHF_TOIS, onCurve))
              .timeSeries(ImmutableMap.of(CHF_TOIS, TIME_SERIES_BUILDER.build()))
              .build();
      OvernightAveragedRateObservation ro =
          OvernightAveragedRateObservation.of(CHF_TOIS, FIXING_START_DATE, FIXING_END_DATE, 0);
      ForwardOvernightAveragedRateObservationFn obsFn =
          ForwardOvernightAveragedRateObservationFn.DEFAULT;

      PointSensitivityBuilder sensitivityBuilderComputed =
          obsFn.rateSensitivity(ro, DUMMY_ACCRUAL_START_DATE, DUMMY_ACCRUAL_END_DATE, prov);
      CurveCurrencyParameterSensitivities parameterSensitivityComputed =
          prov.curveParameterSensitivity(sensitivityBuilderComputed.build());

      CurveCurrencyParameterSensitivities parameterSensitivityExpected =
          CAL_FD.sensitivity(
              prov,
              (p) ->
                  CurrencyAmount.of(
                      CHF_TOIS.getCurrency(),
                      obsFn.rate(ro, DUMMY_ACCRUAL_START_DATE, DUMMY_ACCRUAL_END_DATE, (p))));
      assertTrue(
          parameterSensitivityComputed.equalWithTolerance(
              parameterSensitivityExpected, EPS_FD * 10.0));
    }
  }
/** Test {@link IssuerCurveDiscountFactors}. */
@Test
public class IssuerCurveDiscountFactorsTest {

  private static final LocalDate DATE = date(2015, 6, 4);
  private static final LocalDate DATE_AFTER = date(2015, 7, 30);
  private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
  private static final CurveName NAME = CurveName.of("TestCurve");
  private static final CurveMetadata METADATA = Curves.zeroRates(NAME, ACT_365F);
  private static final InterpolatedNodalCurve CURVE =
      InterpolatedNodalCurve.of(
          METADATA, DoubleArray.of(0, 10), DoubleArray.of(1, 2), INTERPOLATOR);
  private static final DiscountFactors DSC_FACTORS = ZeroRateDiscountFactors.of(GBP, DATE, CURVE);
  private static final LegalEntityGroup GROUP = LegalEntityGroup.of("ISSUER1");

  public void test_of() {
    IssuerCurveDiscountFactors test = IssuerCurveDiscountFactors.of(DSC_FACTORS, GROUP);
    assertEquals(test.getLegalEntityGroup(), GROUP);
    assertEquals(test.getCurrency(), GBP);
    assertEquals(test.getCurveName(), NAME);
    assertEquals(test.getParameterCount(), 2);
    assertEquals(test.getValuationDate(), DATE);
    assertEquals(test.discountFactor(DATE_AFTER), DSC_FACTORS.discountFactor(DATE_AFTER));
  }

  public void test_zeroRatePointSensitivity() {
    IssuerCurveDiscountFactors base = IssuerCurveDiscountFactors.of(DSC_FACTORS, GROUP);
    IssuerCurveZeroRateSensitivity expected =
        IssuerCurveZeroRateSensitivity.of(DSC_FACTORS.zeroRatePointSensitivity(DATE_AFTER), GROUP);
    IssuerCurveZeroRateSensitivity computed = base.zeroRatePointSensitivity(DATE_AFTER);
    assertEquals(computed, expected);
  }

  public void test_zeroRatePointSensitivity_USD() {
    IssuerCurveDiscountFactors base = IssuerCurveDiscountFactors.of(DSC_FACTORS, GROUP);
    IssuerCurveZeroRateSensitivity expected =
        IssuerCurveZeroRateSensitivity.of(
            DSC_FACTORS.zeroRatePointSensitivity(DATE_AFTER, USD), GROUP);
    IssuerCurveZeroRateSensitivity computed = base.zeroRatePointSensitivity(DATE_AFTER, USD);
    assertEquals(computed, expected);
  }

  public void test_curveParameterSensitivity() {
    IssuerCurveDiscountFactors base = IssuerCurveDiscountFactors.of(DSC_FACTORS, GROUP);
    IssuerCurveZeroRateSensitivity sensi = base.zeroRatePointSensitivity(DATE_AFTER, USD);
    CurveCurrencyParameterSensitivities computed = base.curveParameterSensitivity(sensi);
    CurveCurrencyParameterSensitivities expected =
        DSC_FACTORS.curveParameterSensitivity(
            DSC_FACTORS.zeroRatePointSensitivity(DATE_AFTER, USD));
    assertEquals(computed, expected);
  }

  // -------------------------------------------------------------------------
  public void coverage() {
    IssuerCurveDiscountFactors test1 = IssuerCurveDiscountFactors.of(DSC_FACTORS, GROUP);
    coverImmutableBean(test1);
    IssuerCurveDiscountFactors test2 =
        IssuerCurveDiscountFactors.of(
            ZeroRateDiscountFactors.of(USD, DATE, CURVE), LegalEntityGroup.of("ISSUER2"));
    coverBeanEquals(test1, test2);
  }
}
/** Test {@link DiscountFxForwardRates}. */
@Test
public class DiscountFxForwardRatesTest {

  private static final LocalDate DATE_VAL = date(2015, 6, 4);
  private static final LocalDate DATE_REF = date(2015, 7, 30);
  private static final FxRate FX_RATE = FxRate.of(GBP, USD, 1.5d);
  private static final CurrencyPair CURRENCY_PAIR = CurrencyPair.of(GBP, USD);

  private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
  private static final CurveMetadata METADATA1 = Curves.zeroRates("TestCurve", ACT_365F);
  private static final CurveMetadata METADATA2 = Curves.zeroRates("TestCurveUSD", ACT_365F);
  private static final InterpolatedNodalCurve CURVE1 =
      InterpolatedNodalCurve.of(
          METADATA1, DoubleArray.of(0, 10), DoubleArray.of(0.01, 0.02), INTERPOLATOR);
  private static final InterpolatedNodalCurve CURVE2 =
      InterpolatedNodalCurve.of(
          METADATA2, DoubleArray.of(0, 10), DoubleArray.of(0.015, 0.025), INTERPOLATOR);
  private static final ZeroRateDiscountFactors DFCURVE_GBP =
      ZeroRateDiscountFactors.of(GBP, DATE_VAL, CURVE1);
  private static final ZeroRateDiscountFactors DFCURVE_GBP2 =
      ZeroRateDiscountFactors.of(GBP, DATE_VAL, CURVE2);
  private static final ZeroRateDiscountFactors DFCURVE_USD =
      ZeroRateDiscountFactors.of(USD, DATE_VAL, CURVE2);
  private static final ZeroRateDiscountFactors DFCURVE_USD2 =
      ZeroRateDiscountFactors.of(USD, DATE_VAL, CURVE1);

  // -------------------------------------------------------------------------
  public void test_of() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
    assertEquals(test.getValuationDate(), DATE_VAL);
    assertEquals(test.getBaseCurrencyDiscountFactors(), DFCURVE_GBP);
    assertEquals(test.getCounterCurrencyDiscountFactors(), DFCURVE_USD);
    assertEquals(test.getFxRateProvider(), FX_RATE);
    assertEquals(test.findData(CURVE1.getName()), Optional.of(CURVE1));
    assertEquals(test.findData(CURVE2.getName()), Optional.of(CURVE2));
    assertEquals(test.findData(CurveName.of("Rubbish")), Optional.empty());

    int baseSize = DFCURVE_USD.getParameterCount();
    assertEquals(test.getParameterCount(), DFCURVE_GBP.getParameterCount() + baseSize);
    assertEquals(test.getParameter(0), DFCURVE_GBP.getParameter(0));
    assertEquals(test.getParameter(baseSize), DFCURVE_USD.getParameter(0));
    assertEquals(test.getParameterMetadata(0), DFCURVE_GBP.getParameterMetadata(0));
    assertEquals(test.getParameterMetadata(baseSize), DFCURVE_USD.getParameterMetadata(0));
    assertEquals(
        test.withParameter(0, 1d).getBaseCurrencyDiscountFactors(),
        DFCURVE_GBP.withParameter(0, 1d));
    assertEquals(test.withParameter(0, 1d).getCounterCurrencyDiscountFactors(), DFCURVE_USD);
    assertEquals(test.withParameter(baseSize, 1d).getBaseCurrencyDiscountFactors(), DFCURVE_GBP);
    assertEquals(
        test.withParameter(baseSize, 1d).getCounterCurrencyDiscountFactors(),
        DFCURVE_USD.withParameter(0, 1d));
    assertEquals(
        test.withPerturbation((i, v, m) -> v + 1d).getBaseCurrencyDiscountFactors(),
        DFCURVE_GBP.withPerturbation((i, v, m) -> v + 1d));
    assertEquals(
        test.withPerturbation((i, v, m) -> v + 1d).getCounterCurrencyDiscountFactors(),
        DFCURVE_USD.withPerturbation((i, v, m) -> v + 1d));
  }

  public void test_of_nonMatchingCurrency() {
    assertThrowsIllegalArg(
        () -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_GBP));
    assertThrowsIllegalArg(
        () -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_USD, DFCURVE_USD));
  }

  public void test_of_nonMatchingValuationDates() {
    DiscountFactors curve2 = ZeroRateDiscountFactors.of(USD, DATE_REF, CURVE2);
    assertThrowsIllegalArg(
        () -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, curve2));
  }

  public void test_builder() {
    assertThrowsIllegalArg(
        () ->
            DiscountFxForwardRates.meta()
                .builder()
                .setString(DiscountFxForwardRates.meta().currencyPair(), "GBP/USD")
                .build());
    assertThrowsIllegalArg(
        () ->
            DiscountFxForwardRates.meta()
                .builder()
                .setString(DiscountFxForwardRates.meta().currencyPair().name(), "GBP/USD")
                .build());
  }

  // -------------------------------------------------------------------------
  public void test_withDiscountFactors() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    test = test.withDiscountFactors(DFCURVE_GBP2, DFCURVE_USD2);
    assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
    assertEquals(test.getValuationDate(), DATE_VAL);
    assertEquals(test.getBaseCurrencyDiscountFactors(), DFCURVE_GBP2);
    assertEquals(test.getCounterCurrencyDiscountFactors(), DFCURVE_USD2);
    assertEquals(test.getFxRateProvider(), FX_RATE);
  }

  // -------------------------------------------------------------------------
  public void test_rate() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    double dfCcyBaseAtMaturity = DFCURVE_GBP.discountFactor(DATE_REF);
    double dfCcyCounterAtMaturity = DFCURVE_USD.discountFactor(DATE_REF);
    double expected = FX_RATE.fxRate(GBP, USD) * (dfCcyBaseAtMaturity / dfCcyCounterAtMaturity);
    assertEquals(test.rate(GBP, DATE_REF), expected, 1e-12);
    assertEquals(test.rate(USD, DATE_REF), 1d / expected, 1e-12);
  }

  public void test_rate_nonMatchingCurrency() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    assertThrowsIllegalArg(() -> test.rate(EUR, DATE_VAL));
  }

  // -------------------------------------------------------------------------
  public void test_ratePointSensitivity() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    assertEquals(
        test.ratePointSensitivity(GBP, DATE_REF),
        FxForwardSensitivity.of(CURRENCY_PAIR, GBP, DATE_REF, 1d));
    assertEquals(
        test.ratePointSensitivity(USD, DATE_REF),
        FxForwardSensitivity.of(CURRENCY_PAIR, USD, DATE_REF, 1d));
  }

  public void test_ratePointSensitivity_nonMatchingCurrency() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    assertThrowsIllegalArg(() -> test.ratePointSensitivity(EUR, DATE_VAL));
  }

  // -------------------------------------------------------------------------
  public void test_rateFxSpotSensitivity() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    double dfCcyBaseAtMaturity = DFCURVE_GBP.discountFactor(DATE_REF);
    double dfCcyCounterAtMaturity = DFCURVE_USD.discountFactor(DATE_REF);
    double expected = dfCcyBaseAtMaturity / dfCcyCounterAtMaturity;
    assertEquals(test.rateFxSpotSensitivity(GBP, DATE_REF), expected, 1e-12);
    assertEquals(test.rateFxSpotSensitivity(USD, DATE_REF), 1d / expected, 1e-12);
  }

  public void test_rateFxSpotSensitivity_nonMatchingCurrency() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    assertThrowsIllegalArg(() -> test.rateFxSpotSensitivity(EUR, DATE_VAL));
  }

  // -------------------------------------------------------------------------
  // proper end-to-end tests are elsewhere
  public void test_parameterSensitivity() {
    DiscountFxForwardRates test =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    FxForwardSensitivity point = FxForwardSensitivity.of(CURRENCY_PAIR, GBP, DATE_VAL, 1d);
    assertEquals(test.parameterSensitivity(point).size(), 2);
    FxForwardSensitivity point2 = FxForwardSensitivity.of(CURRENCY_PAIR, USD, DATE_VAL, 1d);
    assertEquals(test.parameterSensitivity(point2).size(), 2);
  }

  // -------------------------------------------------------------------------
  public void coverage() {
    DiscountFxForwardRates test1 =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
    coverImmutableBean(test1);
    DiscountFxForwardRates test2 =
        DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE.inverse(), DFCURVE_GBP2, DFCURVE_USD2);
    coverBeanEquals(test1, test2);
    DiscountFxForwardRates test3 =
        DiscountFxForwardRates.of(
            CurrencyPair.of(USD, EUR),
            FxRate.of(EUR, USD, 1.2d),
            DFCURVE_USD,
            ZeroRateDiscountFactors.of(EUR, DATE_VAL, CURVE2));
    coverBeanEquals(test1, test3);
  }
}
/** 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.");
  }
}