/**
   * This calculates the sensitivity of the present value (PV) to the lognormal Black implied
   * volatities at the knot points of the surface.
   *
   * <p>The return format is a DoubleMatrix2D with rows equal to the total number of maturities and
   * columns equal to the number of strikes.
   *
   * <p>Note - the change of the surface due to the movement of a single node is
   * interpolator-dependent, so an instrument may have non-local sensitivity
   *
   * @param swap the VarianceSwap
   * @param market the VarianceSwapDataBundle
   * @param shift Size of shift made in centered-finite difference approximation. e.g. 1% would be
   *     0.01, and 1bp 0.0001
   * @return A NodalDoublesSurface with same axes as market.getVolatilitySurface(). Contains
   *     currency amount per unit amount change in the black volatility of each node
   */
  public NodalDoublesSurface calcBlackVegaForEntireSurface(
      final VarianceSwap swap, final VarianceSwapDataBundle market, final double shift) {
    Validate.notNull(swap, "null VarianceSwap");
    Validate.notNull(market, "null VarianceSwapDataBundle");

    // Unpack market data
    final Surface<Double, Double, Double> surface = market.getVolatilitySurface().getSurface();
    Validate.isTrue(
        surface instanceof InterpolatedDoublesSurface,
        "Currently will only accept a Equity VolatilitySurfaces based on an InterpolatedDoublesSurface");

    final InterpolatedDoublesSurface blackSurf = (InterpolatedDoublesSurface) surface;
    final Double[] maturities = blackSurf.getXData();
    final Double[] strikes = blackSurf.getYData();
    final int nNodes = maturities.length;
    Validate.isTrue(nNodes == strikes.length);

    // Bump and reprice
    final Double[] vegas = new Double[nNodes];
    for (int j = 0; j < nNodes; j++) {
      vegas[j] = calcBlackVegaForSinglePoint(swap, market, maturities[j], strikes[j], shift);
    }

    return new NodalDoublesSurface(maturities, strikes, vegas);
  }
 public void theta() {
   double priceFutures =
       METHOD_FUTURE.price(CALL_JB_147.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES);
   double expiry = CALL_JB_147.getExpirationTime();
   double volatility = BLACK_SURFACE_EXP_STRIKE.getZValue(expiry, STRIKE_147);
   double rate =
       -Math.log(
               ISSUER_SPECIFIC_MULTICURVES
                   .getMulticurveProvider()
                   .getDiscountFactor(CALL_JB_147.getCurrency(), CALL_JB_147.getExpirationTime()))
           / CALL_JB_147.getExpirationTime();
   double thetaCallExpected =
       BlackFormulaRepository.theta(
           priceFutures,
           STRIKE_147,
           CALL_JB_147.getExpirationTime(),
           volatility,
           CALL_JB_147.isCall(),
           rate);
   double thetaCallComputed = METHOD_OPT.theta(CALL_JB_147, BLACK_EXP_STRIKE_BNDFUT);
   assertEquals(
       "BondFuturesOptionMarginSecurityBlackFlatMethod: theta",
       thetaCallExpected,
       thetaCallComputed,
       TOLERANCE_DELTA);
 }
 public void delta() {
   final double priceFutures =
       METHOD_FUTURE.price(CALL_JB_147.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES);
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(
           STRIKE_147, CALL_JB_147.getExpirationTime(), CALL_JB_147.isCall());
   final double expiry = CALL_JB_147.getExpirationTime();
   final double volatility = BLACK_SURFACE_EXP_STRIKE.getZValue(expiry, STRIKE_147);
   double df =
       ISSUER_SPECIFIC_MULTICURVES
           .getMulticurveProvider()
           .getDiscountFactor(JBM5_DEFINITION.getCurrency(), CALL_JB_147.getExpirationTime());
   final BlackFunctionData dataBlack = new BlackFunctionData(priceFutures, df, volatility);
   final double[] priceAD = BLACK_FUNCTION.getPriceAdjoint(option, dataBlack);
   final double deltaCallExpected = priceAD[1];
   final double deltaCallComputed = METHOD_OPT.delta(CALL_JB_147, BLACK_EXP_STRIKE_BNDFUT);
   assertEquals(
       "BondFuturesOptionPremiumSecurityBlackBondFuturesMethod: delta",
       deltaCallExpected,
       deltaCallComputed,
       TOLERANCE_DELTA);
   final double deltaPutComputed = METHOD_OPT.delta(PUT_JB_147, BLACK_EXP_STRIKE_BNDFUT);
   assertEquals(
       "BondFuturesOptionPremiumSecurityBlackBondFuturesMethod: delta",
       deltaCallExpected - deltaPutComputed,
       df,
       TOLERANCE_DELTA);
 }
 public static InterpolatedDoublesSurface createBlackSurfaceExpiryTenorShift(final double shift) {
   return InterpolatedDoublesSurface.from(
       new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
       new double[] {2, 2, 2, 10, 10, 10},
       new double[] {
         0.35 + shift, 0.34 + shift, 0.25 + shift, 0.30 + shift, 0.25 + shift, 0.20 + shift
       },
       INTERPOLATOR_LINEAR_2D);
 }
 public void impliedVolatility() {
   final double strike = STRIKE_147;
   final double expiry = CALL_JB_147.getExpirationTime();
   final double ivExpected = BLACK_SURFACE_EXP_STRIKE.getZValue(expiry, strike);
   final double ivComputed = METHOD_OPT.impliedVolatility(CALL_JB_147, BLACK_EXP_STRIKE_BNDFUT);
   assertEquals(
       "BondFuturesOptionPremiumSecurityBlackSurfaceMethod: impliedVolatility",
       ivExpected,
       ivComputed,
       TOLERANCE_RATE);
 }
 /**
  * Create the same surface as createBlackSwaptionEUR6() but with one volatility shifted.
  *
  * @param index The index of the shifted volatility.
  * @param shift The shift.
  * @return The surface.
  */
 public static BlackFlatSwaptionParameters createBlackSwaptionEUR6Shift(
     final int index, final double shift) {
   final double[] vol = new double[] {0.35, 0.34, 0.25, 0.30, 0.25, 0.20};
   vol[index] += shift;
   final InterpolatedDoublesSurface surfaceShift =
       InterpolatedDoublesSurface.from(
           new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
           new double[] {2, 2, 2, 10, 10, 10},
           vol,
           INTERPOLATOR_LINEAR_2D);
   return new BlackFlatSwaptionParameters(surfaceShift, EUR1YEURIBOR6M);
 }
 public static InterpolatedDoublesSurface createBlackSurfaceExpiryStrikeShift(final double shift) {
   return InterpolatedDoublesSurface.from(
       new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
       new double[] {0.01, 0.01, 0.01, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03},
       new double[] {
         0.35 + shift,
         0.34 + shift,
         0.25 + shift,
         0.30 + shift,
         0.25 + shift,
         0.20 + shift,
         0.28 + shift,
         0.23 + shift,
         0.18 + shift
       },
       INTERPOLATOR_LINEAR_2D);
 }
 @Override
 public Set<ComputedValue> execute(
     final FunctionExecutionContext executionContext,
     final FunctionInputs inputs,
     final ComputationTarget target,
     final Set<ValueRequirement> desiredValues) {
   final Clock snapshotClock = executionContext.getValuationClock();
   final ZonedDateTime now = ZonedDateTime.now(snapshotClock);
   final Object volatilitySurfaceDataObject = inputs.getValue(_requirement);
   if (volatilitySurfaceDataObject == null) {
     throw new OpenGammaRuntimeException("Could not get " + _requirement);
   }
   @SuppressWarnings("unchecked")
   final VolatilitySurfaceData<LocalDate, Double> volatilitySurfaceData =
       (VolatilitySurfaceData<LocalDate, Double>) volatilitySurfaceDataObject;
   final int n = volatilitySurfaceData.getXs().length;
   final int m = volatilitySurfaceData.getYs().length;
   final DoubleArrayList t = new DoubleArrayList();
   final DoubleArrayList k = new DoubleArrayList();
   final DoubleArrayList sigma = new DoubleArrayList();
   final LocalDate[] xDates = volatilitySurfaceData.getXs();
   final Double[] y = volatilitySurfaceData.getYs();
   for (int i = 0; i < n; i++) {
     final Double time = DateUtils.getDifferenceInYears(now.toLocalDate(), xDates[i]);
     for (int j = 0; j < m; j++) {
       final Double strike = y[j];
       final Double vol = volatilitySurfaceData.getVolatility(xDates[i], y[j]);
       if (time != null && strike != null && vol != null) {
         t.add(time);
         k.add(strike);
         sigma.add(vol);
       }
     }
   }
   final Surface<Double, Double, Double> surface =
       InterpolatedDoublesSurface.from(
           t.toDoubleArray(), k.toDoubleArray(), sigma.toDoubleArray(), _interpolator);
   final VolatilitySurface volatilitySurface = new VolatilitySurface(surface);
   return Collections.singleton(new ComputedValue(_result, volatilitySurface));
 }
 public void priceFromFuturesPrice() {
   final double price = 1.465;
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(
           STRIKE_147, CALL_JB_147.getExpirationTime(), CALL_JB_147.isCall());
   final double logmoney = Math.log(STRIKE_147 / price);
   final double expiry = CALL_JB_147.getExpirationTime();
   final double volatility = BLACK_SURFACE_EXP_STRIKE.getZValue(expiry, logmoney);
   double df =
       ISSUER_SPECIFIC_MULTICURVES
           .getMulticurveProvider()
           .getDiscountFactor(JBM5_DEFINITION.getCurrency(), expiry);
   final BlackFunctionData dataBlack = new BlackFunctionData(price, df, volatility);
   final double priceExpected = BLACK_FUNCTION.getPriceFunction(option).evaluate(dataBlack);
   final double priceComputed =
       METHOD_OPT.priceFromUnderlyingPrice(CALL_JB_147, BLACK_EXP_STRIKE_BNDFUT, price);
   assertEquals(
       "BondFuturesOptionPremiumSecurityBlackBondFuturesMethod: underlying futures price",
       priceExpected,
       priceComputed,
       TOLERANCE_RATE);
 }
/** Sets of market data used in tests. */
public class TestsDataSetsBlack {

  private static final Interpolator1D LINEAR_FLAT =
      CombinedInterpolatorExtrapolatorFactory.getInterpolator(
          Interpolator1DFactory.LINEAR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR);
  private static final GridInterpolator2D INTERPOLATOR_LINEAR_2D =
      new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT);

  private static final Calendar CALENDAR = new MondayToFridayCalendar("TARGET");
  private static final GeneratorSwapFixedIborMaster GENERATOR_SWAP_MASTER =
      GeneratorSwapFixedIborMaster.getInstance();
  private static final GeneratorSwapFixedIbor EUR1YEURIBOR6M =
      GENERATOR_SWAP_MASTER.getGenerator("EUR1YEURIBOR6M", CALENDAR);
  private static final GeneratorSwapFixedIbor EUR1YEURIBOR3M =
      GENERATOR_SWAP_MASTER.getGenerator("EUR1YEURIBOR3M", CALENDAR);

  private static final InterpolatedDoublesSurface BLACK_SURFACE_EXP_TEN =
      InterpolatedDoublesSurface.from(
          new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
          new double[] {2, 2, 2, 10, 10, 10},
          new double[] {0.35, 0.34, 0.25, 0.30, 0.25, 0.20},
          INTERPOLATOR_LINEAR_2D);
  private static final InterpolatedDoublesSurface BLACK_SURFACE_EXP_STR =
      InterpolatedDoublesSurface.from(
          new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
          new double[] {0.01, 0.01, 0.01, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03},
          new double[] {0.35, 0.34, 0.25, 0.30, 0.25, 0.20, 0.28, 0.23, 0.18},
          INTERPOLATOR_LINEAR_2D);
  private static final BlackFlatSwaptionParameters BLACK_SWAPTION_EUR6 =
      new BlackFlatSwaptionParameters(BLACK_SURFACE_EXP_TEN, EUR1YEURIBOR6M);
  private static final BlackFlatSwaptionParameters BLACK_SWAPTION_EUR3 =
      new BlackFlatSwaptionParameters(BLACK_SURFACE_EXP_TEN, EUR1YEURIBOR3M);

  public static InterpolatedDoublesSurface createBlackSurfaceExpiryTenor() {
    return BLACK_SURFACE_EXP_TEN;
  }

  public static InterpolatedDoublesSurface createBlackSurfaceExpiryStrike() {
    return BLACK_SURFACE_EXP_STR;
  }

  public static InterpolatedDoublesSurface createBlackSurfaceExpiryTenorShift(final double shift) {
    return InterpolatedDoublesSurface.from(
        new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
        new double[] {2, 2, 2, 10, 10, 10},
        new double[] {
          0.35 + shift, 0.34 + shift, 0.25 + shift, 0.30 + shift, 0.25 + shift, 0.20 + shift
        },
        INTERPOLATOR_LINEAR_2D);
  }

  public static InterpolatedDoublesSurface createBlackSurfaceExpiryStrikeShift(final double shift) {
    return InterpolatedDoublesSurface.from(
        new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
        new double[] {0.01, 0.01, 0.01, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03},
        new double[] {
          0.35 + shift,
          0.34 + shift,
          0.25 + shift,
          0.30 + shift,
          0.25 + shift,
          0.20 + shift,
          0.28 + shift,
          0.23 + shift,
          0.18 + shift
        },
        INTERPOLATOR_LINEAR_2D);
  }

  public static BlackFlatSwaptionParameters createBlackSwaptionEUR6() {
    return BLACK_SWAPTION_EUR6;
  }

  public static BlackFlatSwaptionParameters createBlackSwaptionEUR3() {
    return BLACK_SWAPTION_EUR3;
  }

  /**
   * Create the same surface as createBlackSwaptionEUR6() but with a given parallel shift.
   *
   * @param shift The shift.
   * @return The surface.
   */
  public static BlackFlatSwaptionParameters createBlackSwaptionEUR6Shift(final double shift) {
    final InterpolatedDoublesSurface surfaceShift = createBlackSurfaceExpiryTenorShift(shift);
    return new BlackFlatSwaptionParameters(surfaceShift, EUR1YEURIBOR6M);
  }

  /**
   * Create the same surface as createBlackSwaptionEUR6() but with one volatility shifted.
   *
   * @param index The index of the shifted volatility.
   * @param shift The shift.
   * @return The surface.
   */
  public static BlackFlatSwaptionParameters createBlackSwaptionEUR6Shift(
      final int index, final double shift) {
    final double[] vol = new double[] {0.35, 0.34, 0.25, 0.30, 0.25, 0.20};
    vol[index] += shift;
    final InterpolatedDoublesSurface surfaceShift =
        InterpolatedDoublesSurface.from(
            new double[] {0.5, 1.0, 5.0, 0.5, 1.0, 5.0},
            new double[] {2, 2, 2, 10, 10, 10},
            vol,
            INTERPOLATOR_LINEAR_2D);
    return new BlackFlatSwaptionParameters(surfaceShift, EUR1YEURIBOR6M);
  }

  public static YieldCurveBundle createCurvesEUR() {
    final String discountingCurvename = "EUR Discounting";
    final String forward3MCurveName = "Forward EURIBOR3M";
    final String forward6MCurveName = "Forward EURIBOR6M";
    final InterpolatedDoublesCurve dscC =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 20.0},
            new double[] {0.0050, 0.0100, 0.0150, 0.0200, 0.0200, 0.0300},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            discountingCurvename);
    final InterpolatedDoublesCurve fwd3C =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 25.0},
            new double[] {0.0070, 0.0120, 0.0165, 0.0215, 0.0210, 0.0310},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            forward3MCurveName);
    final InterpolatedDoublesCurve fwd6C =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 30.0},
            new double[] {0.0075, 0.0125, 0.0170, 0.0220, 0.0212, 0.0312},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            forward6MCurveName);
    final YieldCurveBundle curves = new YieldCurveBundle();
    curves.setCurve(discountingCurvename, YieldCurve.from(dscC));
    curves.setCurve(forward3MCurveName, YieldCurve.from(fwd3C));
    curves.setCurve(forward6MCurveName, YieldCurve.from(fwd6C));
    return curves;
  }

  public static String[] curvesEURNames() {
    final String discountingCurvename = "EUR Discounting";
    final String forward3MCurveName = "Forward EURIBOR3M";
    final String forward6MCurveName = "Forward EURIBOR6M";
    return new String[] {discountingCurvename, forward3MCurveName, forward6MCurveName};
  }

  public static YieldCurveBundle createCurvesUSD() {
    final String discountingCurvename = "USD Discounting";
    final String forward3MCurveName = "Forward USDLIBOR3M";
    final String forward6MCurveName = "Forward USDLIBOR6M";
    final InterpolatedDoublesCurve dscC =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 20.0},
            new double[] {0.0050, 0.0100, 0.0150, 0.0200, 0.0200, 0.0300},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            discountingCurvename);
    final InterpolatedDoublesCurve fwd3C =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 25.0},
            new double[] {0.0070, 0.0120, 0.0165, 0.0215, 0.0210, 0.0310},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            forward3MCurveName);
    final InterpolatedDoublesCurve fwd6C =
        new InterpolatedDoublesCurve(
            new double[] {0.05, 1.0, 2.0, 5.0, 10.0, 30.0},
            new double[] {0.0075, 0.0125, 0.0170, 0.0220, 0.0212, 0.0312},
            CombinedInterpolatorExtrapolatorFactory.getInterpolator(
                Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR),
            true,
            forward6MCurveName);
    final YieldCurveBundle curves = new YieldCurveBundle();
    curves.setCurve(discountingCurvename, YieldCurve.from(dscC));
    curves.setCurve(forward3MCurveName, YieldCurve.from(fwd3C));
    curves.setCurve(forward6MCurveName, YieldCurve.from(fwd6C));
    return curves;
  }

  /**
   * Create a yield curve bundle with three curves. One called "Credit" with a constant rate of 5%,
   * one called "Discounting" with a constant rate of 4%, and one called "Forward" with a constant
   * rate of 4.5%.
   *
   * @return The yield curve bundle.
   */
  public static YieldCurveBundle createCurvesBond() {
    final String CREDIT_CURVE_NAME = "Credit";
    final String DISCOUNTING_CURVE_NAME = "Repo";
    final String FORWARD_CURVE_NAME = "Forward";
    final YieldAndDiscountCurve CURVE_5 = YieldCurve.from(ConstantDoublesCurve.from(0.05));
    final YieldAndDiscountCurve CURVE_4 = YieldCurve.from(ConstantDoublesCurve.from(0.04));
    final YieldAndDiscountCurve CURVE_45 = YieldCurve.from(ConstantDoublesCurve.from(0.045));
    final YieldCurveBundle curves = new YieldCurveBundle();
    curves.setCurve(CREDIT_CURVE_NAME, CURVE_5);
    curves.setCurve(DISCOUNTING_CURVE_NAME, CURVE_4);
    curves.setCurve(FORWARD_CURVE_NAME, CURVE_45);
    return curves;
  }

  public static YieldCurveWithBlackCubeBundle createCubesBondFutureOption() {
    return new YieldCurveWithBlackCubeBundle(BLACK_SURFACE_EXP_TEN, createCurvesBond());
  }
}
/** Tests for interest rate future options analytics functions using the black calculator. */
@Test(groups = TestGroup.UNIT)
public class IRFutureOptionFnTest {

  private static final ZonedDateTime VALUATION_TIME = DateUtils.getUTCDate(2014, 1, 22);
  public static final LocalDate TRADE_DATE = LocalDate.of(2000, 1, 1);
  public static final OffsetTime TRADE_TIME = OffsetTime.of(LocalTime.of(0, 0), ZoneOffset.UTC);
  private static final LocalDate MARKET_DATA_DATE = LocalDate.of(2014, 2, 18);
  private static final double STD_TOLERANCE_PV = 1.0E-3;
  private IRFutureOptionFn _blackIRFutureOptionFn;
  private IRFutureOptionFn _normalIRFutureOptionFn;
  private InterestRateFutureSecurity _irFuture = createIRFuture();
  private IRFutureOptionTrade _irFutureOptionTrade = createIRFutureOptionTrade();
  private FunctionRunner _functionRunner;
  private static final CalculationArguments ARGS =
      CalculationArguments.builder()
          .valuationTime(VALUATION_TIME)
          .marketDataSpecification(LiveMarketDataSpecification.LIVE_SPEC)
          .build();
  private static final Interpolator1D LINEAR_FLAT =
      CombinedInterpolatorExtrapolatorFactory.getInterpolator(
          Interpolator1DFactory.LINEAR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR,
          Interpolator1DFactory.FLAT_EXTRAPOLATOR);
  private static final GridInterpolator2D INTERPOLATOR_2D =
      new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT);
  private static final InterpolatedDoublesSurface TEST_SURFACE =
      InterpolatedDoublesSurface.from(
          new double[] {.1, .2, .3},
          new double[] {.1, .2, .3},
          new double[] {.1, .2, .3},
          INTERPOLATOR_2D);

  private static MarketDataEnvironment createSuppliedData() {
    LocalDateDoubleTimeSeries optionPrice =
        ImmutableLocalDateDoubleTimeSeries.of(VALUATION_TIME.toLocalDate(), 0.975);
    RawId<Double> optionRawId =
        RawId.of(ExternalSchemes.syntheticSecurityId("Test future option").toBundle());
    MarketDataEnvironmentBuilder builder = new MarketDataEnvironmentBuilder();
    builder.add(optionRawId, optionPrice);
    builder.add(VolatilitySurfaceId.of("TestExchange"), new VolatilitySurface(TEST_SURFACE));
    builder.valuationTime(VALUATION_TIME);
    return builder.build();
  }

  @BeforeClass
  public void setUpClass() {

    ImmutableMap<Class<?>, Object> components = generateComponents();
    VersionCorrectionProvider vcProvider = new FixedInstantVersionCorrectionProvider(Instant.now());
    ServiceContext serviceContext =
        ServiceContext.of(components).with(VersionCorrectionProvider.class, vcProvider);
    ThreadLocalServiceContext.init(serviceContext);

    ComponentMap componentMap = ComponentMap.of(components);
    MarketDataSource marketDataSource =
        InterestRateMockSources.createMarketDataSource(MARKET_DATA_DATE, true);
    TestMarketDataFactory marketDataFactory = new TestMarketDataFactory(marketDataSource);
    ConfigLink<CurrencyMatrix> currencyMatrixLink =
        ConfigLink.resolved(componentMap.getComponent(CurrencyMatrix.class));
    List<MarketDataBuilder> builders =
        MarketDataBuilders.standard(componentMap, "dataSource", currencyMatrixLink);

    MarketDataEnvironmentFactory environmentFactory =
        new MarketDataEnvironmentFactory(marketDataFactory, builders);

    _functionRunner = new FunctionRunner(environmentFactory);
    _normalIRFutureOptionFn =
        FunctionModel.build(IRFutureOptionFn.class, normalConfig(), componentMap);
    _blackIRFutureOptionFn =
        FunctionModel.build(IRFutureOptionFn.class, blackConfig(), componentMap);
  }

  private FunctionModelConfig blackConfig() {
    FunctionModelConfig config =
        config(
            arguments(
                function(
                    MarketExposureSelector.class,
                    argument(
                        "exposureFunctions",
                        ConfigLink.resolved(InterestRateMockSources.mockExposureFunctions()))),
                function(
                    RootFinderConfiguration.class,
                    argument("rootFinderAbsoluteTolerance", 1e-9),
                    argument("rootFinderRelativeTolerance", 1e-9),
                    argument("rootFinderMaxIterations", 1000)),
                function(
                    DefaultCurveNodeConverterFn.class,
                    argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofYears(1))))),
            implementations(
                IRFutureOptionFn.class, DefaultIRFutureOptionFn.class,
                IRFutureOptionCalculatorFactory.class, IRFutureOptionBlackCalculatorFactory.class,
                CurveSpecificationMarketDataFn.class, DefaultCurveSpecificationMarketDataFn.class,
                FXMatrixFn.class, DefaultFXMatrixFn.class,
                BlackSTIRFuturesProviderFn.class, TestBlackSTIRFuturesProviderFn.class,
                DiscountingMulticurveCombinerFn.class,
                    ExposureFunctionsDiscountingMulticurveCombinerFn.class,
                CurveDefinitionFn.class, DefaultCurveDefinitionFn.class,
                CurveLabellingFn.class, CurveDefinitionCurveLabellingFn.class,
                CurveSpecificationFn.class, DefaultCurveSpecificationFn.class,
                CurveConstructionConfigurationSource.class,
                    ConfigDBCurveConstructionConfigurationSource.class,
                CurveNodeConverterFn.class, DefaultCurveNodeConverterFn.class,
                HistoricalMarketDataFn.class, DefaultHistoricalMarketDataFn.class,
                FixingsFn.class, DefaultFixingsFn.class,
                MarketDataFn.class, DefaultMarketDataFn.class,
                CurveSelector.class, MarketExposureSelector.class,
                DiscountingMulticurveCombinerFn.class, CurveSelectorMulticurveBundleFn.class));

    return config;
  }

  private FunctionModelConfig normalConfig() {
    FunctionModelConfig config =
        config(
            arguments(
                function(
                    MarketExposureSelector.class,
                    argument(
                        "exposureFunctions",
                        ConfigLink.resolved(InterestRateMockSources.mockExposureFunctions()))),
                function(
                    RootFinderConfiguration.class,
                    argument("rootFinderAbsoluteTolerance", 1e-9),
                    argument("rootFinderRelativeTolerance", 1e-9),
                    argument("rootFinderMaxIterations", 1000)),
                function(
                    TestIRFutureOptionNormalSurfaceProviderFn.class,
                    argument("moneynessOnPrice", false)),
                function(
                    DefaultCurveNodeConverterFn.class,
                    argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofYears(1))))),
            implementations(
                IRFutureOptionFn.class, DefaultIRFutureOptionFn.class,
                IRFutureOptionCalculatorFactory.class, IRFutureOptionNormalCalculatorFactory.class,
                CurveSpecificationMarketDataFn.class, DefaultCurveSpecificationMarketDataFn.class,
                FXMatrixFn.class, DefaultFXMatrixFn.class,
                CurveDefinitionFn.class, DefaultCurveDefinitionFn.class,
                CurveLabellingFn.class, CurveDefinitionCurveLabellingFn.class,
                CurveSpecificationFn.class, DefaultCurveSpecificationFn.class,
                CurveConstructionConfigurationSource.class,
                    ConfigDBCurveConstructionConfigurationSource.class,
                CurveNodeConverterFn.class, DefaultCurveNodeConverterFn.class,
                HistoricalMarketDataFn.class, DefaultHistoricalMarketDataFn.class,
                FixingsFn.class, DefaultFixingsFn.class,
                MarketDataFn.class, DefaultMarketDataFn.class,
                CurveSelector.class, MarketExposureSelector.class,
                IRFutureOptionNormalSurfaceProviderFn.class,
                    TestIRFutureOptionNormalSurfaceProviderFn.class,
                DiscountingMulticurveCombinerFn.class, CurveSelectorMulticurveBundleFn.class));

    return config;
  }

  private ImmutableMap<Class<?>, Object> generateComponents() {
    ImmutableMap.Builder<Class<?>, Object> builder = ImmutableMap.builder();
    for (Map.Entry<Class<?>, Object> entry :
        InterestRateMockSources.generateBaseComponents().entrySet()) {
      Class<?> key = entry.getKey();
      if (key.equals(SecuritySource.class)) {
        appendSecuritySource((SecuritySource) entry.getValue());
      }
      builder.put(key, entry.getValue());
    }
    return builder.build();
  }

  // TODO - this assumes knowledge of the underlying source, should find a better way to do this
  private void appendSecuritySource(SecuritySource source) {
    SecurityMaster master = ((MasterSecuritySource) source).getMaster();
    master.add(new SecurityDocument(_irFuture));
  }

  private InterestRateFutureSecurity createIRFuture() {
    Expiry expiry =
        new Expiry(ZonedDateTime.of(LocalDate.of(2014, 6, 18), LocalTime.of(0, 0), ZoneOffset.UTC));
    String tradingExchange = "";
    String settlementExchange = "";
    Currency currency = Currency.USD;
    double unitAmount = 1000;
    ExternalId underlyingId = InterestRateMockSources.getLiborIndexId();
    String category = "";
    InterestRateFutureSecurity irFuture =
        new InterestRateFutureSecurity(
            expiry,
            tradingExchange,
            settlementExchange,
            currency,
            unitAmount,
            underlyingId,
            category);
    // Need this for time series lookup
    ExternalId irFutureId = ExternalSchemes.syntheticSecurityId("Test future");
    irFuture.setExternalIdBundle(irFutureId.toBundle());
    return irFuture;
  }

  private IRFutureOptionTrade createIRFutureOptionTrade() {

    String exchange = "TestExchange";
    ExerciseType exerciseType = new EuropeanExerciseType();
    double pointValue = Double.NaN;
    boolean margined = true;
    double strike = 0.99;
    OptionType optionType = OptionType.PUT;
    ExternalId irFutureId = Iterables.getOnlyElement(_irFuture.getExternalIdBundle());
    IRFutureOptionSecurity irFutureOption =
        new IRFutureOptionSecurity(
            exchange,
            _irFuture.getExpiry(),
            exerciseType,
            irFutureId,
            pointValue,
            margined,
            _irFuture.getCurrency(),
            strike,
            optionType);
    // Need this for time series lookup
    irFutureOption.setExternalIdBundle(
        ExternalSchemes.syntheticSecurityId("Test future option").toBundle());

    Counterparty counterparty =
        new SimpleCounterparty(ExternalId.of(Counterparty.DEFAULT_SCHEME, "COUNTERPARTY"));
    BigDecimal tradeQuantity = BigDecimal.valueOf(1);
    SimpleTrade trade =
        new SimpleTrade(irFutureOption, tradeQuantity, counterparty, TRADE_DATE, TRADE_TIME);
    trade.setPremium(10.0);
    trade.setPremiumCurrency(Currency.USD);
    return new IRFutureOptionTrade(trade);
  }

  @Test
  public void testBlackPresentValue() {
    Result<MultipleCurrencyAmount> result =
        _functionRunner.runFunction(
            ARGS,
            createSuppliedData(),
            new Function<Environment, Result<MultipleCurrencyAmount>>() {
              @Override
              public Result<MultipleCurrencyAmount> apply(Environment env) {
                return _blackIRFutureOptionFn.calculatePV(env, _irFutureOptionTrade);
              }
            });
    assertSuccess(result);

    MultipleCurrencyAmount mca = result.getValue();
    assertThat(
        mca.getCurrencyAmount(Currency.USD).getAmount(),
        is(closeTo(-972.460677, STD_TOLERANCE_PV)));
  }

  @Test
  public void testBlackBucketedZeroDelta() {
    Result<BucketedCurveSensitivities> result =
        _functionRunner.runFunction(
            ARGS,
            createSuppliedData(),
            new Function<Environment, Result<BucketedCurveSensitivities>>() {
              @Override
              public Result<BucketedCurveSensitivities> apply(Environment env) {
                return _blackIRFutureOptionFn.calculateBucketedZeroIRDelta(
                    env, _irFutureOptionTrade);
              }
            });
    assertSuccess(result);
  }

  @Test
  public void testNormalPresentValue() {
    Result<MultipleCurrencyAmount> result =
        _functionRunner.runFunction(
            ARGS,
            createSuppliedData(),
            new Function<Environment, Result<MultipleCurrencyAmount>>() {
              @Override
              public Result<MultipleCurrencyAmount> apply(Environment env) {
                return _normalIRFutureOptionFn.calculatePV(env, _irFutureOptionTrade);
              }
            });
    assertSuccess(result);
    MultipleCurrencyAmount mca = result.getValue();
    assertThat(
        mca.getCurrencyAmount(Currency.USD).getAmount(),
        is(closeTo(-902.7156551, STD_TOLERANCE_PV)));
  }

  @Test
  public void testNormalPrice() {
    Result<Double> result =
        _functionRunner.runFunction(
            ARGS,
            createSuppliedData(),
            new Function<Environment, Result<Double>>() {
              @Override
              public Result<Double> apply(Environment env) {
                return _normalIRFutureOptionFn.calculateModelPrice(env, _irFutureOptionTrade);
              }
            });
    assertSuccess(result);
    Double price = result.getValue();
    assertThat(price, is(closeTo(0.072284344, STD_TOLERANCE_PV)));
  }

  @Test
  public void testNormalBucketedZeroDelta() {
    Result<BucketedCurveSensitivities> result =
        _functionRunner.runFunction(
            ARGS,
            createSuppliedData(),
            new Function<Environment, Result<BucketedCurveSensitivities>>() {
              @Override
              public Result<BucketedCurveSensitivities> apply(Environment env) {
                return _normalIRFutureOptionFn.calculateBucketedZeroIRDelta(
                    env, _irFutureOptionTrade);
              }
            });
    assertSuccess(result);
  }
}