// -------------------------------------------------------------------------
 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_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_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));
 }
 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);
 }
 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);
   }
 }
/** 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
  }
}