// -------------------------------------------------------------------------
 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_volatility_sensitivity() {
   double eps = 1.0e-6;
   int nData = TIME.size();
   for (int i = 0; i < NB_TEST; i++) {
     SwaptionSensitivity point =
         SwaptionSensitivity.of(
             CONVENTION,
             TEST_OPTION_EXPIRY[i],
             TENOR.get(i),
             TEST_STRIKE,
             TEST_FORWARD,
             GBP,
             TEST_SENSITIVITY[i]);
     SurfaceCurrencyParameterSensitivity sensi =
         PROVIDER_WITH_PARAM.surfaceCurrencyParameterSensitivity(point);
     Map<DoublesPair, Double> map = new HashMap<DoublesPair, Double>();
     for (int j = 0; j < nData; ++j) {
       DoubleArray volDataUp = VOL.subArray(0, nData).with(j, VOL.get(j) + eps);
       DoubleArray volDataDw = VOL.subArray(0, nData).with(j, VOL.get(j) - eps);
       InterpolatedNodalSurface paramUp =
           InterpolatedNodalSurface.of(
               METADATA_WITH_PARAM, TIME, TENOR, volDataUp, INTERPOLATOR_2D);
       InterpolatedNodalSurface paramDw =
           InterpolatedNodalSurface.of(
               METADATA_WITH_PARAM, TIME, TENOR, volDataDw, INTERPOLATOR_2D);
       BlackVolatilityExpiryTenorSwaptionProvider provUp =
           BlackVolatilityExpiryTenorSwaptionProvider.of(
               paramUp, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
       BlackVolatilityExpiryTenorSwaptionProvider provDw =
           BlackVolatilityExpiryTenorSwaptionProvider.of(
               paramDw, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
       double volUp =
           provUp.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
       double volDw =
           provDw.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
       double fd = 0.5 * (volUp - volDw) / eps;
       map.put(DoublesPair.of(TIME.get(j), TENOR.get(j)), fd);
     }
     SurfaceCurrencyParameterSensitivity sensiFromNoMetadata =
         PROVIDER.surfaceCurrencyParameterSensitivity(point);
     List<SurfaceParameterMetadata> list = sensi.getMetadata().getParameterMetadata().get();
     DoubleArray computed = sensi.getSensitivity();
     assertEquals(computed.size(), nData);
     for (int j = 0; j < list.size(); ++j) {
       SwaptionSurfaceExpiryTenorNodeMetadata metadata =
           (SwaptionSurfaceExpiryTenorNodeMetadata) list.get(i);
       double expected = map.get(DoublesPair.of(metadata.getYearFraction(), metadata.getTenor()));
       assertEquals(computed.get(i), expected, eps);
       assertTrue(
           sensiFromNoMetadata.getMetadata().getParameterMetadata().get().contains(metadata));
     }
   }
 }
 // -------------------------------------------------------------------------
 public void coverage() {
   BlackBondFutureExpiryLogMoneynessVolatilities test1 =
       BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, SURFACE);
   coverImmutableBean(test1);
   BlackBondFutureExpiryLogMoneynessVolatilities test2 =
       BlackBondFutureExpiryLogMoneynessVolatilities.of(
           VAL_DATE_TIME.plusDays(1), SURFACE.withParameter(0, 1d));
   coverBeanEquals(test1, test2);
 }
 public void test_volatility() {
   for (int i = 0; i < NB_TEST; i++) {
     double expiryTime = PROVIDER_WITH_PARAM.relativeTime(TEST_OPTION_EXPIRY[i]);
     double volExpected = SURFACE_WITH_PARAM.zValue(expiryTime, TEST_TENOR[i]);
     double volComputed =
         PROVIDER_WITH_PARAM.getVolatility(
             TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
     assertEquals(volComputed, volExpected, TOLERANCE_VOL);
   }
 }
 /**
  * Returns the swaption normal volatility surface shifted by a given amount. The shift is
  * parallel.
  *
  * @param shift the shift
  * @return the swaption normal volatility surface
  */
 public static NormalVolatilityExpiryTenorSwaptionProvider normalVolSwaptionProviderUsdStsShifted(
     double shift) {
   DoubleArray volShifted = NORMAL_VOL.map(v -> v + shift);
   return NormalVolatilityExpiryTenorSwaptionProvider.of(
       InterpolatedNodalSurface.of(METADATA, TIMES, TENOR, volShifted, INTERPOLATOR_2D),
       USD_1Y_LIBOR3M,
       DayCounts.ACT_365F,
       VALUATION_DATE_STD,
       VALUATION_TIME_STD,
       VALUATION_ZONE_STD);
 }
 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_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_volatility_sensitivity() {
   double eps = 1.0e-6;
   int nData = TIME.size();
   for (int i = 0; i < NB_TEST; i++) {
     double expiry = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]);
     BondFutureOptionSensitivity point =
         BondFutureOptionSensitivity.of(
             VOLS.getName(),
             expiry,
             TEST_FUTURE_EXPIRY[i],
             TEST_STRIKE_PRICE[i],
             TEST_FUTURE_PRICE[i],
             USD,
             TEST_SENSITIVITY[i]);
     CurrencyParameterSensitivity sensActual =
         VOLS.parameterSensitivity(point).getSensitivities().get(0);
     double[] computed = sensActual.getSensitivity().toArray();
     for (int j = 0; j < nData; j++) {
       DoubleArray volDataUp = VOL.with(j, VOL.get(j) + eps);
       DoubleArray volDataDw = VOL.with(j, VOL.get(j) - eps);
       InterpolatedNodalSurface paramUp =
           InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, volDataUp, INTERPOLATOR_2D);
       InterpolatedNodalSurface paramDw =
           InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, volDataDw, INTERPOLATOR_2D);
       BlackBondFutureExpiryLogMoneynessVolatilities provUp =
           BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, paramUp);
       BlackBondFutureExpiryLogMoneynessVolatilities provDw =
           BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, paramDw);
       double volUp =
           provUp.volatility(
               expiry, TEST_FUTURE_EXPIRY[i], TEST_STRIKE_PRICE[i], TEST_FUTURE_PRICE[i]);
       double volDw =
           provDw.volatility(
               expiry, TEST_FUTURE_EXPIRY[i], TEST_STRIKE_PRICE[i], TEST_FUTURE_PRICE[i]);
       double fd = 0.5 * (volUp - volDw) / eps;
       assertEquals(computed[j], fd, eps);
     }
   }
 }
 public void test_volatility() {
   for (int i = 0; i < NB_TEST; i++) {
     double expiryTime = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]);
     double volExpected =
         SURFACE.zValue(expiryTime, Math.log(TEST_STRIKE_PRICE[i] / TEST_FUTURE_PRICE[i]));
     double volComputed =
         VOLS.volatility(
             TEST_OPTION_EXPIRY[i],
             TEST_FUTURE_EXPIRY[i],
             TEST_STRIKE_PRICE[i],
             TEST_FUTURE_PRICE[i]);
     assertEquals(volComputed, volExpected, TOLERANCE_VOL);
   }
 }
 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 BlackVolatilityExpiryTenorSwaptionProvider}. */
@Test
public class BlackVolatilityExpiryTenorSwaptionProviderTest {

  private static final Interpolator1D LINEAR_FLAT =
      CombinedInterpolatorExtrapolator.of(
          CurveInterpolators.LINEAR.getName(),
          CurveExtrapolators.FLAT.getName(),
          CurveExtrapolators.FLAT.getName());
  private static final GridInterpolator2D INTERPOLATOR_2D =
      new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT);
  private static final DoubleArray TIME =
      DoubleArray.of(0.25, 0.5, 1.0, 0.25, 0.5, 1.0, 0.25, 0.5, 1.0, 0.25, 0.5, 1.0);
  private static final DoubleArray TENOR =
      DoubleArray.of(3.0, 3.0, 3.0, 5.0, 5.0, 5.0, 7.0, 7.0, 7.0, 10.0, 10.0, 10.0);
  private static final DoubleArray VOL =
      DoubleArray.of(0.14, 0.12, 0.1, 0.14, 0.13, 0.12, 0.13, 0.12, 0.11, 0.12, 0.11, 0.1);
  private static final SurfaceMetadata METADATA_WITH_PARAM;
  private static final SurfaceMetadata METADATA;

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

  private static final InterpolatedNodalSurface SURFACE_WITH_PARAM =
      InterpolatedNodalSurface.of(METADATA_WITH_PARAM, TIME, TENOR, VOL, INTERPOLATOR_2D);
  private static final InterpolatedNodalSurface SURFACE =
      InterpolatedNodalSurface.of(METADATA, TIME, TENOR, VOL, INTERPOLATOR_2D);
  private static final FixedIborSwapConvention CONVENTION =
      FixedIborSwapConventions.GBP_FIXED_1Y_LIBOR_3M;
  private static final LocalDate VALUATION_DATE = date(2015, 2, 17);
  private static final LocalTime VALUATION_TIME = LocalTime.of(13, 45);
  private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London");
  private static final ZonedDateTime VALUATION_DATE_TIME =
      VALUATION_DATE.atTime(VALUATION_TIME).atZone(LONDON_ZONE);
  private static final BlackVolatilityExpiryTenorSwaptionProvider PROVIDER_WITH_PARAM =
      BlackVolatilityExpiryTenorSwaptionProvider.of(
          SURFACE_WITH_PARAM, CONVENTION, ACT_365F, VALUATION_DATE, VALUATION_TIME, LONDON_ZONE);
  private static final BlackVolatilityExpiryTenorSwaptionProvider PROVIDER =
      BlackVolatilityExpiryTenorSwaptionProvider.of(
          SURFACE, CONVENTION, ACT_365F, VALUATION_DATE, VALUATION_TIME, LONDON_ZONE);

  private static final ZonedDateTime[] TEST_OPTION_EXPIRY =
      new ZonedDateTime[] {
        dateUtc(2015, 2, 17), dateUtc(2015, 5, 17), dateUtc(2015, 6, 17), dateUtc(2017, 2, 17)
      };
  private static final int NB_TEST = TEST_OPTION_EXPIRY.length;
  private static final double[] TEST_TENOR = new double[] {2.0, 6.0, 7.0, 15.0};
  private static final double[] TEST_SENSITIVITY = new double[] {1.0, 1.0, 1.0, 1.0};
  private static final double TEST_FORWARD = 0.025; // not used internally
  private static final double TEST_STRIKE = 0.03; // not used internally

  private static final double TOLERANCE_VOL = 1.0E-10;

  // -------------------------------------------------------------------------
  public void test_valuationDate() {
    assertEquals(PROVIDER_WITH_PARAM.getValuationDateTime(), VALUATION_DATE_TIME);
  }

  public void test_swapConvention() {
    assertEquals(PROVIDER_WITH_PARAM.getConvention(), CONVENTION);
  }

  public void test_tenor() {
    double test1 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, VALUATION_DATE);
    assertEquals(test1, 0d);
    double test2 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, date(2018, 2, 28));
    assertEquals(test2, 3d);
    double test3 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, date(2018, 2, 10));
    assertEquals(test3, 3d);
  }

  public void test_relativeTime() {
    double test1 = PROVIDER_WITH_PARAM.relativeTime(VALUATION_DATE_TIME);
    assertEquals(test1, 0d);
    double test2 = PROVIDER_WITH_PARAM.relativeTime(date(2018, 2, 17).atStartOfDay(LONDON_ZONE));
    double test3 = PROVIDER_WITH_PARAM.relativeTime(date(2012, 2, 17).atStartOfDay(LONDON_ZONE));
    assertEquals(test2, -test3); // consistency checked
  }

  public void test_volatility() {
    for (int i = 0; i < NB_TEST; i++) {
      double expiryTime = PROVIDER_WITH_PARAM.relativeTime(TEST_OPTION_EXPIRY[i]);
      double volExpected = SURFACE_WITH_PARAM.zValue(expiryTime, TEST_TENOR[i]);
      double volComputed =
          PROVIDER_WITH_PARAM.getVolatility(
              TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
      assertEquals(volComputed, volExpected, TOLERANCE_VOL);
    }
  }

  public void test_volatility_sensitivity() {
    double eps = 1.0e-6;
    int nData = TIME.size();
    for (int i = 0; i < NB_TEST; i++) {
      SwaptionSensitivity point =
          SwaptionSensitivity.of(
              CONVENTION,
              TEST_OPTION_EXPIRY[i],
              TENOR.get(i),
              TEST_STRIKE,
              TEST_FORWARD,
              GBP,
              TEST_SENSITIVITY[i]);
      SurfaceCurrencyParameterSensitivity sensi =
          PROVIDER_WITH_PARAM.surfaceCurrencyParameterSensitivity(point);
      Map<DoublesPair, Double> map = new HashMap<DoublesPair, Double>();
      for (int j = 0; j < nData; ++j) {
        DoubleArray volDataUp = VOL.subArray(0, nData).with(j, VOL.get(j) + eps);
        DoubleArray volDataDw = VOL.subArray(0, nData).with(j, VOL.get(j) - eps);
        InterpolatedNodalSurface paramUp =
            InterpolatedNodalSurface.of(
                METADATA_WITH_PARAM, TIME, TENOR, volDataUp, INTERPOLATOR_2D);
        InterpolatedNodalSurface paramDw =
            InterpolatedNodalSurface.of(
                METADATA_WITH_PARAM, TIME, TENOR, volDataDw, INTERPOLATOR_2D);
        BlackVolatilityExpiryTenorSwaptionProvider provUp =
            BlackVolatilityExpiryTenorSwaptionProvider.of(
                paramUp, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
        BlackVolatilityExpiryTenorSwaptionProvider provDw =
            BlackVolatilityExpiryTenorSwaptionProvider.of(
                paramDw, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
        double volUp =
            provUp.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
        double volDw =
            provDw.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD);
        double fd = 0.5 * (volUp - volDw) / eps;
        map.put(DoublesPair.of(TIME.get(j), TENOR.get(j)), fd);
      }
      SurfaceCurrencyParameterSensitivity sensiFromNoMetadata =
          PROVIDER.surfaceCurrencyParameterSensitivity(point);
      List<SurfaceParameterMetadata> list = sensi.getMetadata().getParameterMetadata().get();
      DoubleArray computed = sensi.getSensitivity();
      assertEquals(computed.size(), nData);
      for (int j = 0; j < list.size(); ++j) {
        SwaptionSurfaceExpiryTenorNodeMetadata metadata =
            (SwaptionSurfaceExpiryTenorNodeMetadata) list.get(i);
        double expected = map.get(DoublesPair.of(metadata.getYearFraction(), metadata.getTenor()));
        assertEquals(computed.get(i), expected, eps);
        assertTrue(
            sensiFromNoMetadata.getMetadata().getParameterMetadata().get().contains(metadata));
      }
    }
  }

  // -------------------------------------------------------------------------
  public void coverage() {
    BlackVolatilityExpiryTenorSwaptionProvider test1 =
        BlackVolatilityExpiryTenorSwaptionProvider.of(
            SURFACE_WITH_PARAM, CONVENTION, ACT_365F, VALUATION_DATE_TIME);
    coverImmutableBean(test1);
    BlackVolatilityExpiryTenorSwaptionProvider test2 =
        BlackVolatilityExpiryTenorSwaptionProvider.of(SURFACE, CONVENTION, ACT_360, VALUATION_DATE);
    coverBeanEquals(test1, test2);
  }
}
/** Black volatility data sets for testing. */
public class SwaptionNormalVolatilityDataSets {

  private static final double BP1 = 1.0E-4;

  private static final Interpolator1D LINEAR_FLAT =
      CombinedInterpolatorExtrapolator.of(
          CurveInterpolators.LINEAR.getName(),
          CurveExtrapolators.FLAT.getName(),
          CurveExtrapolators.FLAT.getName());
  private static final GridInterpolator2D INTERPOLATOR_2D =
      new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT);

  //     =====     Standard figures for testing     =====
  private static final DoubleArray TIMES =
      DoubleArray.of(
          0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00,
          10.0, 0.50, 1.00, 5.00, 10.0);
  private static final DoubleArray TENOR =
      DoubleArray.of(
          1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0, 10.0, 30.0,
          30.0, 30.0, 30.0);
  private static final DoubleArray NORMAL_VOL =
      DoubleArray.of(
          0.010, 0.011, 0.012, 0.013, 0.011, 0.012, 0.013, 0.014, 0.012, 0.013, 0.014, 0.015, 0.013,
          0.014, 0.015, 0.016, 0.014, 0.015, 0.016, 0.017);
  private static final SurfaceMetadata METADATA =
      DefaultSurfaceMetadata.builder()
          .xValueType(ValueType.YEAR_FRACTION)
          .yValueType(ValueType.YEAR_FRACTION)
          .zValueType(ValueType.VOLATILITY)
          .surfaceName(SurfaceName.of("Normal Vol"))
          .build();
  private static final NodalSurface SURFACE_STD =
      InterpolatedNodalSurface.of(METADATA, TIMES, TENOR, NORMAL_VOL, INTERPOLATOR_2D);

  private static final LocalDate VALUATION_DATE_STD = RatesProviderDataSets.VAL_DATE_2014_01_22;
  private static final LocalTime VALUATION_TIME_STD = LocalTime.of(13, 45);
  private static final ZoneId VALUATION_ZONE_STD = ZoneId.of("Europe/London");
  private static final BusinessDayAdjustment MOD_FOL_US =
      BusinessDayAdjustment.of(MODIFIED_FOLLOWING, USNY);
  private static final FixedRateSwapLegConvention USD_FIXED_1Y_30U360 =
      FixedRateSwapLegConvention.of(USD, THIRTY_U_360, Frequency.P6M, MOD_FOL_US);
  private static final IborRateSwapLegConvention USD_IBOR_LIBOR3M =
      IborRateSwapLegConvention.of(USD_LIBOR_3M);
  public static final FixedIborSwapConvention USD_1Y_LIBOR3M =
      ImmutableFixedIborSwapConvention.of("USD-Swap", USD_FIXED_1Y_30U360, USD_IBOR_LIBOR3M);
  public static final NormalVolatilityExpiryTenorSwaptionProvider
      NORMAL_VOL_SWAPTION_PROVIDER_USD_STD =
          NormalVolatilityExpiryTenorSwaptionProvider.of(
              SURFACE_STD,
              USD_1Y_LIBOR3M,
              DayCounts.ACT_365F,
              VALUATION_DATE_STD,
              VALUATION_TIME_STD,
              VALUATION_ZONE_STD);

  /**
   * Returns the swaption normal volatility surface shifted by a given amount. The shift is
   * parallel.
   *
   * @param shift the shift
   * @return the swaption normal volatility surface
   */
  public static NormalVolatilityExpiryTenorSwaptionProvider normalVolSwaptionProviderUsdStsShifted(
      double shift) {
    DoubleArray volShifted = NORMAL_VOL.map(v -> v + shift);
    return NormalVolatilityExpiryTenorSwaptionProvider.of(
        InterpolatedNodalSurface.of(METADATA, TIMES, TENOR, volShifted, INTERPOLATOR_2D),
        USD_1Y_LIBOR3M,
        DayCounts.ACT_365F,
        VALUATION_DATE_STD,
        VALUATION_TIME_STD,
        VALUATION_ZONE_STD);
  }

  public static NormalVolatilityExpiryTenorSwaptionProvider normalVolSwaptionProviderUsdStd(
      LocalDate valuationDate) {
    return NormalVolatilityExpiryTenorSwaptionProvider.of(
        SURFACE_STD,
        USD_1Y_LIBOR3M,
        DayCounts.ACT_365F,
        valuationDate,
        VALUATION_TIME_STD,
        VALUATION_ZONE_STD);
  }

  //     =====     Flat volatilities for testing     =====

  private static final DoubleArray TIMES_FLAT = DoubleArray.of(0.0, 100.0, 0.0, 100.0);
  private static final DoubleArray TENOR_FLAT = DoubleArray.of(0.0, 0.0, 30.0, 30.0);
  private static final DoubleArray NORMAL_VOL_FLAT = DoubleArray.of(0.01, 0.01, 0.01, 0.01);
  private static final InterpolatedNodalSurface SURFACE_FLAT =
      InterpolatedNodalSurface.of(
          METADATA, TIMES_FLAT, TENOR_FLAT, NORMAL_VOL_FLAT, INTERPOLATOR_2D);

  public static final NormalVolatilityExpiryTenorSwaptionProvider
      NORMAL_VOL_SWAPTION_PROVIDER_USD_FLAT =
          NormalVolatilityExpiryTenorSwaptionProvider.of(
              SURFACE_FLAT,
              USD_1Y_LIBOR3M,
              DayCounts.ACT_365F,
              VALUATION_DATE_STD,
              VALUATION_TIME_STD,
              VALUATION_ZONE_STD);

  //     =====     Market data as of 2014-03-20     =====

  private static final DoubleArray TIMES_20150320 =
      DoubleArray.of(
          0.25, 0.25, 0.25, 0.25, 0.25, 0.50, 0.50, 0.50, 0.50, 0.50, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0,
          2.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0, 10.0, 10.0);
  private static final DoubleArray TENORS_20150320 =
      DoubleArray.of(
          1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0,
          5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0);
  private static final DoubleArray NORMAL_VOL_20150320_BP =
      DoubleArray.of(
          43.6, 65.3, 88, 87.5, 88, // 3M
          55.5, 72.2, 90.3, 89.3, 88.6, // 6M
          72.6, 82.7, 91.6, 89.8, 87.3, // 1Y
          90.4, 91.9, 93.4, 84.7, 93.5, // 2Y
          99.3, 96.8, 94.3, 88.6, 77.3, // 5Y
          88.4, 85.9, 82.2, 76.7, 65.1); // 10Y
  private static final DoubleArray NORMAL_VOL_20150320 = NORMAL_VOL_20150320_BP.map(v -> v * BP1);
  private static final NodalSurface SURFACE_20150320 =
      InterpolatedNodalSurface.of(
          METADATA, TIMES_20150320, TENORS_20150320, NORMAL_VOL_20150320, INTERPOLATOR_2D);

  private static final LocalDate VALUATION_DATE_20150320 = LocalDate.of(2015, 3, 20);
  private static final LocalTime VALUATION_TIME_20150320 = LocalTime.of(18, 00);
  private static final ZoneId VALUATION_ZONE_20150320 = ZoneId.of("Europe/London");

  public static final NormalVolatilityExpiryTenorSwaptionProvider
      NORMAL_VOL_SWAPTION_PROVIDER_USD_20150320 =
          NormalVolatilityExpiryTenorSwaptionProvider.of(
              SURFACE_20150320,
              USD_1Y_LIBOR3M,
              DayCounts.ACT_365F,
              VALUATION_DATE_20150320,
              VALUATION_TIME_20150320,
              VALUATION_ZONE_20150320);
}
/** 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 {@link BlackBondFutureExpiryLogMoneynessVolatilities}. */
@Test
public class BlackBondFutureExpiryLogMoneynessVolatilitiesTest {

  private static final SurfaceInterpolator INTERPOLATOR_2D =
      GridSurfaceInterpolator.of(LINEAR, LINEAR);
  private static final DoubleArray TIME =
      DoubleArray.of(0.25, 0.25, 0.25, 0.25, 0.50, 0.50, 0.50, 0.50, 1.00, 1.00, 1.00, 1.00);
  private static final DoubleArray MONEYNESS =
      DoubleArray.of(-0.02, -0.01, 0.00, 0.01, -0.02, -0.01, 0.00, 0.01, -0.02, -0.01, 0.00, 0.01);
  private static final DoubleArray VOL =
      DoubleArray.of(
          0.01, 0.011, 0.012, 0.010, 0.011, 0.012, 0.013, 0.012, 0.012, 0.013, 0.014, 0.014);
  private static final SurfaceMetadata METADATA;

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

  private static final InterpolatedNodalSurface SURFACE =
      InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, VOL, INTERPOLATOR_2D);
  private static final LocalDate VAL_DATE = date(2015, 2, 17);
  private static final LocalTime VAL_TIME = LocalTime.of(13, 45);
  private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London");
  private static final ZonedDateTime VAL_DATE_TIME = VAL_DATE.atTime(VAL_TIME).atZone(LONDON_ZONE);
  private static final BlackBondFutureExpiryLogMoneynessVolatilities VOLS =
      BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, SURFACE);

  private static final ZonedDateTime[] TEST_OPTION_EXPIRY =
      new ZonedDateTime[] {
        dateUtc(2015, 2, 17), dateUtc(2015, 5, 17), dateUtc(2015, 6, 17), dateUtc(2017, 2, 17)
      };
  private static final int NB_TEST = TEST_OPTION_EXPIRY.length;
  private static final LocalDate[] TEST_FUTURE_EXPIRY =
      new LocalDate[] {date(2015, 2, 17), date(2015, 5, 17), date(2015, 5, 17), date(2015, 5, 17)};
  private static final double[] TEST_STRIKE_PRICE = new double[] {0.985, 0.985, 0.985, 0.985};
  private static final double[] TEST_FUTURE_PRICE = new double[] {0.98, 0.985, 1.00, 1.01};
  //  private static final double[] TEST_SENSITIVITY = new double[] {9.2, 16.0, 1.8, 5.7 };
  private static final double[] TEST_SENSITIVITY = new double[] {1.0, 1.0, 1.0, 1.0};

  private static final double TOLERANCE_VOL = 1.0E-10;

  // -------------------------------------------------------------------------
  public void test_valuationDate() {
    assertEquals(VOLS.getValuationDateTime(), VAL_DATE_TIME);
  }

  public void test_volatility() {
    for (int i = 0; i < NB_TEST; i++) {
      double expiryTime = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]);
      double volExpected =
          SURFACE.zValue(expiryTime, Math.log(TEST_STRIKE_PRICE[i] / TEST_FUTURE_PRICE[i]));
      double volComputed =
          VOLS.volatility(
              TEST_OPTION_EXPIRY[i],
              TEST_FUTURE_EXPIRY[i],
              TEST_STRIKE_PRICE[i],
              TEST_FUTURE_PRICE[i]);
      assertEquals(volComputed, volExpected, TOLERANCE_VOL);
    }
  }

  public void test_volatility_sensitivity() {
    double eps = 1.0e-6;
    int nData = TIME.size();
    for (int i = 0; i < NB_TEST; i++) {
      double expiry = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]);
      BondFutureOptionSensitivity point =
          BondFutureOptionSensitivity.of(
              VOLS.getName(),
              expiry,
              TEST_FUTURE_EXPIRY[i],
              TEST_STRIKE_PRICE[i],
              TEST_FUTURE_PRICE[i],
              USD,
              TEST_SENSITIVITY[i]);
      CurrencyParameterSensitivity sensActual =
          VOLS.parameterSensitivity(point).getSensitivities().get(0);
      double[] computed = sensActual.getSensitivity().toArray();
      for (int j = 0; j < nData; j++) {
        DoubleArray volDataUp = VOL.with(j, VOL.get(j) + eps);
        DoubleArray volDataDw = VOL.with(j, VOL.get(j) - eps);
        InterpolatedNodalSurface paramUp =
            InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, volDataUp, INTERPOLATOR_2D);
        InterpolatedNodalSurface paramDw =
            InterpolatedNodalSurface.of(METADATA, TIME, MONEYNESS, volDataDw, INTERPOLATOR_2D);
        BlackBondFutureExpiryLogMoneynessVolatilities provUp =
            BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, paramUp);
        BlackBondFutureExpiryLogMoneynessVolatilities provDw =
            BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, paramDw);
        double volUp =
            provUp.volatility(
                expiry, TEST_FUTURE_EXPIRY[i], TEST_STRIKE_PRICE[i], TEST_FUTURE_PRICE[i]);
        double volDw =
            provDw.volatility(
                expiry, TEST_FUTURE_EXPIRY[i], TEST_STRIKE_PRICE[i], TEST_FUTURE_PRICE[i]);
        double fd = 0.5 * (volUp - volDw) / eps;
        assertEquals(computed[j], fd, eps);
      }
    }
  }

  // -------------------------------------------------------------------------
  public void coverage() {
    BlackBondFutureExpiryLogMoneynessVolatilities test1 =
        BlackBondFutureExpiryLogMoneynessVolatilities.of(VAL_DATE_TIME, SURFACE);
    coverImmutableBean(test1);
    BlackBondFutureExpiryLogMoneynessVolatilities test2 =
        BlackBondFutureExpiryLogMoneynessVolatilities.of(
            VAL_DATE_TIME.plusDays(1), SURFACE.withParameter(0, 1d));
    coverBeanEquals(test1, test2);
  }
}