// -------------------------------------------------------------------------
 public void test_priceSensitivity() {
   PointSensitivities point =
       OPTION_PRICER.priceSensitivityStickyStrike(
           FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
   CurveCurrencyParameterSensitivities computed = RATE_PROVIDER.curveParameterSensitivity(point);
   CurveCurrencyParameterSensitivities expected =
       FD_CAL.sensitivity(
           RATE_PROVIDER,
           (p) ->
               CurrencyAmount.of(
                   EUR, OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, (p), VOL_PROVIDER)));
   double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
   double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
   double expiryTime =
       ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
   double logMoneyness = Math.log(strike / futurePrice);
   double logMoneynessUp = Math.log(strike / (futurePrice + EPS));
   double logMoneynessDw = Math.log(strike / (futurePrice - EPS));
   double vol = SURFACE.zValue(expiryTime, logMoneyness);
   double volUp = SURFACE.zValue(expiryTime, logMoneynessUp);
   double volDw = SURFACE.zValue(expiryTime, logMoneynessDw);
   double volSensi = 0.5 * (volUp - volDw) / EPS;
   double vega = BlackFormulaRepository.vega(futurePrice, strike, expiryTime, vol);
   CurveCurrencyParameterSensitivities sensiVol =
       RATE_PROVIDER
           .curveParameterSensitivity(
               FUTURE_PRICER.priceSensitivity(
                   FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER))
           .multipliedBy(-vega * volSensi);
   expected = expected.combinedWith(sensiVol);
   assertTrue(computed.equalWithTolerance(expected, 30d * EPS));
 }
 public void test_priceSensitivity_from_future_price() {
   double futurePrice = 1.1d;
   PointSensitivities point =
       OPTION_PRICER.priceSensitivityStickyStrike(
           FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
   CurveCurrencyParameterSensitivities computed = RATE_PROVIDER.curveParameterSensitivity(point);
   double delta =
       OPTION_PRICER.deltaStickyStrike(
           FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
   CurveCurrencyParameterSensitivities expected =
       RATE_PROVIDER
           .curveParameterSensitivity(
               FUTURE_PRICER.priceSensitivity(
                   FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER))
           .multipliedBy(delta);
   assertTrue(computed.equalWithTolerance(expected, TOL));
 }
 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_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_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_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_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));
 }
/** Test {@link BlackBondFutureOptionMarginedProductPricer}. */
@Test
public class BlackBondFutureOptionMarginedProductPricerTest {
  // product
  private static final StandardId FUTURE_SECURITY_ID = BondDataSets.FUTURE_SECURITY_ID_EUR;
  private static final BondFutureOption FUTURE_OPTION_PRODUCT =
      BondDataSets.FUTURE_OPTION_PRODUCT_EUR_116;
  // curves
  private static final LegalEntityDiscountingProvider RATE_PROVIDER =
      LegalEntityDiscountingProviderDataSets.ISSUER_REPO_ZERO_EUR;
  // vol surface
  private static final Interpolator1D LINEAR_FLAT =
      CombinedInterpolatorExtrapolator.of(
          CurveInterpolators.LINEAR, CurveExtrapolators.FLAT, CurveExtrapolators.FLAT);
  private static final GridInterpolator2D INTERPOLATOR_2D =
      new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT);
  private static final DoubleArray TIME =
      DoubleArray.of(0.20, 0.20, 0.20, 0.20, 0.20, 0.45, 0.45, 0.45, 0.45, 0.45);
  private static final DoubleArray MONEYNESS =
      DoubleArray.of(-0.050, -0.005, 0.000, 0.005, 0.050, -0.050, -0.005, 0.000, 0.005, 0.050);
  private static final DoubleArray VOL =
      DoubleArray.of(0.50, 0.49, 0.47, 0.48, 0.51, 0.45, 0.44, 0.42, 0.43, 0.46);
  private static final SurfaceMetadata METADATA;

  static {
    List<GenericVolatilitySurfaceYearFractionMetadata> list =
        new ArrayList<GenericVolatilitySurfaceYearFractionMetadata>();
    int nData = TIME.size();
    for (int i = 0; i < nData; ++i) {
      GenericVolatilitySurfaceYearFractionMetadata parameterMetadata =
          GenericVolatilitySurfaceYearFractionMetadata.of(
              TIME.get(i), LogMoneynessStrike.of(MONEYNESS.get(i)));
      list.add(parameterMetadata);
    }
    METADATA =
        DefaultSurfaceMetadata.builder()
            .dayCount(ACT_365F)
            .parameterMetadata(list)
            .surfaceName(SurfaceName.of("GOVT1-BOND-FUT-VOL"))
            .xValueType(ValueType.YEAR_FRACTION)
            .yValueType(ValueType.STRIKE)
            .build();
  }

  private static final InterpolatedNodalSurface SURFACE =
      InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, VOL, INTERPOLATOR_2D);
  private static final LocalDate VALUATION_DATE = RATE_PROVIDER.getValuationDate();
  private static final LocalTime VALUATION_TIME = LocalTime.of(0, 0);
  private static final ZoneId ZONE = FUTURE_OPTION_PRODUCT.getExpiryZone();
  private static final ZonedDateTime VALUATION_DATE_TIME =
      VALUATION_DATE.atTime(VALUATION_TIME).atZone(ZONE);
  private static final BlackVolatilityExpLogMoneynessBondFutureProvider VOL_PROVIDER =
      BlackVolatilityExpLogMoneynessBondFutureProvider.of(
          SURFACE, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
  private static final double TOL = 1.0E-13;
  private static final double EPS = 1.0e-6;
  // pricer
  private static final DiscountingBondFutureProductPricer FUTURE_PRICER =
      DiscountingBondFutureProductPricer.DEFAULT;
  private static final BlackBondFutureOptionMarginedProductPricer OPTION_PRICER =
      new BlackBondFutureOptionMarginedProductPricer(FUTURE_PRICER);
  private static final RatesFiniteDifferenceSensitivityCalculator FD_CAL =
      new RatesFiniteDifferenceSensitivityCalculator(EPS);

  public void test_getFuturePricer() {
    assertSame(OPTION_PRICER.getFuturePricer(), FUTURE_PRICER);
  }

  public void test_price() {
    double computed = OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.price(futurePrice, strike, expiryTime, vol, true);
    assertEquals(computed, expected, TOL);
  }

  public void test_price_from_future_price() {
    double futurePrice = 1.1d;
    double computed =
        OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.price(futurePrice, strike, expiryTime, vol, true);
    assertEquals(computed, expected, TOL);
  }

  public void test_price_from_generic_provider() {
    BondFutureProvider volProvider =
        BlackVolatilityExpLogMoneynessBondFutureProvider.of(
            SURFACE, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
    double computed = OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, volProvider);
    double expected = OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    assertEquals(computed, expected, TOL);
  }

  // -------------------------------------------------------------------------
  public void test_delta() {
    double computed =
        OPTION_PRICER.deltaStickyStrike(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.delta(futurePrice, strike, expiryTime, vol, true);
    assertEquals(computed, expected, TOL);
  }

  public void test_delta_from_future_price() {
    double futurePrice = 1.1d;
    double computed =
        OPTION_PRICER.deltaStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.delta(futurePrice, strike, expiryTime, vol, true);
    assertEquals(computed, expected, TOL);
  }

  public void test_gamma() {
    double computed =
        OPTION_PRICER.gammaStickyStrike(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.gamma(futurePrice, strike, expiryTime, vol);
    assertEquals(computed, expected, TOL);
  }

  public void test_gamma_from_future_price() {
    double futurePrice = 1.1d;
    double computed =
        OPTION_PRICER.gammaStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.gamma(futurePrice, strike, expiryTime, vol);
    assertEquals(computed, expected, TOL);
  }

  public void test_theta() {
    double computed = OPTION_PRICER.theta(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.driftlessTheta(futurePrice, strike, expiryTime, vol);
    assertEquals(computed, expected, TOL);
  }

  public void test_theta_from_future_price() {
    double futurePrice = 1.1d;
    double computed =
        OPTION_PRICER.theta(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double expected = BlackFormulaRepository.driftlessTheta(futurePrice, strike, expiryTime, vol);
    assertEquals(computed, expected, TOL);
  }

  // -------------------------------------------------------------------------
  public void test_priceSensitivity() {
    PointSensitivities point =
        OPTION_PRICER.priceSensitivityStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    CurveCurrencyParameterSensitivities computed = RATE_PROVIDER.curveParameterSensitivity(point);
    CurveCurrencyParameterSensitivities expected =
        FD_CAL.sensitivity(
            RATE_PROVIDER,
            (p) ->
                CurrencyAmount.of(
                    EUR, OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, (p), VOL_PROVIDER)));
    double futurePrice = FUTURE_PRICER.price(FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER);
    double strike = FUTURE_OPTION_PRODUCT.getStrikePrice();
    double expiryTime =
        ACT_365F.relativeYearFraction(VALUATION_DATE, FUTURE_OPTION_PRODUCT.getExpiryDate());
    double logMoneyness = Math.log(strike / futurePrice);
    double logMoneynessUp = Math.log(strike / (futurePrice + EPS));
    double logMoneynessDw = Math.log(strike / (futurePrice - EPS));
    double vol = SURFACE.zValue(expiryTime, logMoneyness);
    double volUp = SURFACE.zValue(expiryTime, logMoneynessUp);
    double volDw = SURFACE.zValue(expiryTime, logMoneynessDw);
    double volSensi = 0.5 * (volUp - volDw) / EPS;
    double vega = BlackFormulaRepository.vega(futurePrice, strike, expiryTime, vol);
    CurveCurrencyParameterSensitivities sensiVol =
        RATE_PROVIDER
            .curveParameterSensitivity(
                FUTURE_PRICER.priceSensitivity(
                    FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER))
            .multipliedBy(-vega * volSensi);
    expected = expected.combinedWith(sensiVol);
    assertTrue(computed.equalWithTolerance(expected, 30d * EPS));
  }

  public void test_priceSensitivity_from_future_price() {
    double futurePrice = 1.1d;
    PointSensitivities point =
        OPTION_PRICER.priceSensitivityStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    CurveCurrencyParameterSensitivities computed = RATE_PROVIDER.curveParameterSensitivity(point);
    double delta =
        OPTION_PRICER.deltaStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    CurveCurrencyParameterSensitivities expected =
        RATE_PROVIDER
            .curveParameterSensitivity(
                FUTURE_PRICER.priceSensitivity(
                    FUTURE_OPTION_PRODUCT.getUnderlying(), RATE_PROVIDER))
            .multipliedBy(delta);
    assertTrue(computed.equalWithTolerance(expected, TOL));
  }

  public void test_priceSensitivity_from_generic_provider() {
    BondFutureProvider volProvider =
        BlackVolatilityExpLogMoneynessBondFutureProvider.of(
            SURFACE, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
    PointSensitivities expected =
        OPTION_PRICER.priceSensitivityStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    PointSensitivities computed =
        OPTION_PRICER.priceSensitivity(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, volProvider);
    assertEquals(computed, expected);
  }

  // -------------------------------------------------------------------------
  public void test_priceSensitivityBlackVolatility() {
    BondFutureOptionSensitivity sensi =
        OPTION_PRICER.priceSensitivityBlackVolatility(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    testPriceSensitivityBlackVolatility(
        VOL_PROVIDER.surfaceCurrencyParameterSensitivity(sensi),
        (p) -> OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, (p)));
  }

  public void test_priceSensitivityBlackVolatility_from_future_price() {
    double futurePrice = 1.1d;
    BondFutureOptionSensitivity sensi =
        OPTION_PRICER.priceSensitivityBlackVolatility(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER, futurePrice);
    testPriceSensitivityBlackVolatility(
        VOL_PROVIDER.surfaceCurrencyParameterSensitivity(sensi),
        (p) -> OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, (p), futurePrice));
  }

  private void testPriceSensitivityBlackVolatility(
      SurfaceCurrencyParameterSensitivity computed,
      Function<BlackVolatilityBondFutureProvider, Double> valueFn) {
    List<SurfaceParameterMetadata> list = computed.getMetadata().getParameterMetadata().get();
    int nVol = VOL.size();
    assertEquals(list.size(), nVol);
    for (int i = 0; i < nVol; ++i) {
      double[] volUp = Arrays.copyOf(VOL.toArray(), nVol);
      double[] volDw = Arrays.copyOf(VOL.toArray(), nVol);
      volUp[i] += EPS;
      volDw[i] -= EPS;
      InterpolatedNodalSurface sfUp =
          InterpolatedNodalSurface.of(
              METADATA, TIME, MONEYNESS, DoubleArray.copyOf(volUp), INTERPOLATOR_2D);
      InterpolatedNodalSurface sfDw =
          InterpolatedNodalSurface.of(
              METADATA, TIME, MONEYNESS, DoubleArray.copyOf(volDw), INTERPOLATOR_2D);
      BlackVolatilityExpLogMoneynessBondFutureProvider provUp =
          BlackVolatilityExpLogMoneynessBondFutureProvider.of(
              sfUp, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
      BlackVolatilityExpLogMoneynessBondFutureProvider provDw =
          BlackVolatilityExpLogMoneynessBondFutureProvider.of(
              sfDw, FUTURE_SECURITY_ID, ACT_365F, VALUATION_DATE_TIME);
      double expected = 0.5 * (valueFn.apply(provUp) - valueFn.apply(provDw)) / EPS;
      int index = -1;
      for (int j = 0; j < nVol; ++j) {
        GenericVolatilitySurfaceYearFractionMetadata meta =
            (GenericVolatilitySurfaceYearFractionMetadata) list.get(j);
        if (meta.getYearFraction() == TIME.get(i)
            && meta.getStrike().getValue() == MONEYNESS.get(i)) {
          index = j;
          continue;
        }
      }
      assertEquals(computed.getSensitivity().get(index), expected, EPS);
    }
  }

  // -------------------------------------------------------------------------
  public void test_marginIndex() {
    double price = 0.12d;
    double computed = OPTION_PRICER.marginIndex(FUTURE_OPTION_PRODUCT, price);
    assertEquals(computed, price * FUTURE_OPTION_PRODUCT.getUnderlying().getNotional());
  }

  public void test_marginIndexSensitivity() {
    PointSensitivities point =
        OPTION_PRICER.priceSensitivityStickyStrike(
            FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    PointSensitivities computed =
        OPTION_PRICER.marginIndexSensitivity(FUTURE_OPTION_PRODUCT, point);
    assertEquals(computed, point.multipliedBy(FUTURE_OPTION_PRODUCT.getUnderlying().getNotional()));
  }

  // -------------------------------------------------------------------------
  public void regression_price() {
    double price = OPTION_PRICER.price(FUTURE_OPTION_PRODUCT, RATE_PROVIDER, VOL_PROVIDER);
    assertEquals(price, 0.08916005173932573, TOL); // 2.x
  }
}
/** 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.");
  }
}