/**
  * 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;
 }
 @Test
 public void test() {
   assertEquals(DATA.getDate(), DATE);
   assertEquals(DATA.getForward(), F, 0);
   assertEquals(DATA.getVolatilitySurface(), SURFACE);
   assertEquals(DATA.getInterestRateCurve(), CURVE);
   final double t = Math.random();
   assertEquals(DATA.getDiscountFactor(t), Math.exp(-R * t), 0);
   BlackOptionDataBundle other = new BlackOptionDataBundle(F, CURVE, SURFACE, DATE);
   assertEquals(other, DATA);
   assertEquals(other.hashCode(), DATA.hashCode());
   other = new BlackOptionDataBundle(DATA);
   assertEquals(other, DATA);
   assertEquals(other.hashCode(), DATA.hashCode());
   other = new BlackOptionDataBundle(F + 1, CURVE, SURFACE, DATE);
   assertFalse(other.equals(DATA));
   other =
       new BlackOptionDataBundle(
           F, YieldCurve.from(ConstantDoublesCurve.from(0.06)), SURFACE, DATE);
   assertFalse(other.equals(DATA));
   other =
       new BlackOptionDataBundle(
           F, CURVE, new VolatilitySurface(ConstantDoublesSurface.from(0.6)), DATE);
   assertFalse(other.equals(DATA));
   other = new BlackOptionDataBundle(F, CURVE, SURFACE, DATE.plusDays(1));
   assertFalse(other.equals(DATA));
 }
 @Test
 public void test() {
   boolean isCall;
   double spot, strike, b, price;
   Expiry expiry;
   YieldAndDiscountCurve curve;
   EuropeanVanillaOptionDefinition definition;
   StandardOptionDataBundle initialData, data;
   double sigma = 0.01;
   for (int i = 0; i < 100; i++) {
     expiry = new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, RANDOM.nextDouble() * 2));
     sigma += 0.03;
     spot = 2 * RANDOM.nextDouble() + 10;
     strike = 2 * RANDOM.nextDouble() + 10;
     curve = YieldCurve.from(ConstantDoublesCurve.from(RANDOM.nextDouble() / 10));
     b = 0; // RANDOM.nextDouble() / 20;
     isCall = RANDOM.nextDouble() < 0.5 ? true : false;
     definition = new EuropeanVanillaOptionDefinition(strike, expiry, isCall);
     initialData = new StandardOptionDataBundle(curve, b, null, spot, DATE);
     data =
         new StandardOptionDataBundle(
             curve, b, new VolatilitySurface(ConstantDoublesSurface.from(sigma)), spot, DATE);
     price = BSM.getPricingFunction(definition).evaluate(data);
     assertEquals(
         sigma,
         MODEL
             .getSurface(
                 Collections.<OptionDefinition, Double>singletonMap(definition, price),
                 initialData)
             .getVolatility(DoublesPair.of(0., 0.)),
         EPS);
   }
 }
/** Test. */
@Test(groups = TestGroup.UNIT)
public class ForwardStartOptionModelTest {
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 7, 1);
  private static final double R = 0.08;
  private static final YieldCurve CURVE = YieldCurve.from(ConstantDoublesCurve.from(R));
  private static final VolatilitySurface SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(0.3));
  private static final double B = 0.04;
  private static final double SPOT = 60;
  private static final double PERCENT = 0.1;
  private static final ZonedDateTime START = DateUtils.getDateOffsetWithYearFraction(DATE, 0.25);
  private static final ZonedDateTime EXPIRY = DateUtils.getDateOffsetWithYearFraction(DATE, 1);
  private static final StandardOptionDataBundle DATA =
      new StandardOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE);
  private static final ForwardStartOptionDefinition FORWARD =
      new ForwardStartOptionDefinition(
          new Expiry(EXPIRY), true, new Expiry(START), PERCENT, Moneyness.OTM);
  private static final ForwardStartOptionDefinition NOW =
      new ForwardStartOptionDefinition(
          new Expiry(EXPIRY), true, new Expiry(DATE), PERCENT, Moneyness.OTM);
  private static final ForwardStartOptionDefinition END =
      new ForwardStartOptionDefinition(
          new Expiry(EXPIRY), true, new Expiry(EXPIRY), PERCENT, Moneyness.OTM);
  private static final EuropeanVanillaOptionDefinition VANILLA =
      new EuropeanVanillaOptionDefinition(SPOT * (1 + PERCENT), new Expiry(EXPIRY), true);
  private static final AnalyticOptionModel<ForwardStartOptionDefinition, StandardOptionDataBundle>
      MODEL = new ForwardStartOptionModel();
  private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM =
      new BlackScholesMertonModel();

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDefinition() {
    MODEL.getPricingFunction(null);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    MODEL.getPricingFunction(FORWARD).evaluate((StandardOptionDataBundle) null);
  }

  @Test
  public void test() {
    assertEquals(MODEL.getPricingFunction(END).evaluate(DATA), 0, 0);
    assertEquals(
        MODEL
            .getPricingFunction(FORWARD)
            .evaluate(
                DATA.withVolatilitySurface(
                    new VolatilitySurface(ConstantDoublesSurface.from(1e-9)))),
        0,
        0);
    assertEquals(
        MODEL.getPricingFunction(NOW).evaluate(DATA),
        BSM.getPricingFunction(VANILLA).evaluate(DATA),
        1e-4);
    assertEquals(MODEL.getPricingFunction(FORWARD).evaluate(DATA), 4.4064, 1e-4);
  }
}
/** Test. */
@Test(groups = TestGroup.UNIT)
public class BlackOptionDataBundleTest {
  private static final double R = 0.05;
  private static final YieldAndDiscountCurve CURVE = YieldCurve.from(ConstantDoublesCurve.from(R));
  private static final VolatilitySurface SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(0.35));
  private static final double F = 100;
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 5, 1);
  private static final BlackOptionDataBundle DATA =
      new BlackOptionDataBundle(F, CURVE, SURFACE, DATE);

  @Test
  public void test() {
    assertEquals(DATA.getDate(), DATE);
    assertEquals(DATA.getForward(), F, 0);
    assertEquals(DATA.getVolatilitySurface(), SURFACE);
    assertEquals(DATA.getInterestRateCurve(), CURVE);
    final double t = Math.random();
    assertEquals(DATA.getDiscountFactor(t), Math.exp(-R * t), 0);
    BlackOptionDataBundle other = new BlackOptionDataBundle(F, CURVE, SURFACE, DATE);
    assertEquals(other, DATA);
    assertEquals(other.hashCode(), DATA.hashCode());
    other = new BlackOptionDataBundle(DATA);
    assertEquals(other, DATA);
    assertEquals(other.hashCode(), DATA.hashCode());
    other = new BlackOptionDataBundle(F + 1, CURVE, SURFACE, DATE);
    assertFalse(other.equals(DATA));
    other =
        new BlackOptionDataBundle(
            F, YieldCurve.from(ConstantDoublesCurve.from(0.06)), SURFACE, DATE);
    assertFalse(other.equals(DATA));
    other =
        new BlackOptionDataBundle(
            F, CURVE, new VolatilitySurface(ConstantDoublesSurface.from(0.6)), DATE);
    assertFalse(other.equals(DATA));
    other = new BlackOptionDataBundle(F, CURVE, SURFACE, DATE.plusDays(1));
    assertFalse(other.equals(DATA));
  }

  @Test
  public void testBuilders() {
    final ZonedDateTime newDate = DATE.plusDays(1);
    assertEquals(DATA.withDate(newDate), new BlackOptionDataBundle(F, CURVE, SURFACE, newDate));
    final double newForward = F + 1;
    assertEquals(
        DATA.withForward(newForward), new BlackOptionDataBundle(newForward, CURVE, SURFACE, DATE));
    final YieldCurve newCurve = YieldCurve.from(ConstantDoublesCurve.from(0.05));
    assertEquals(
        DATA.withInterestRateCurve(newCurve),
        new BlackOptionDataBundle(F, newCurve, SURFACE, DATE));
    final VolatilitySurface newSurface = new VolatilitySurface(ConstantDoublesSurface.from(0.9));
    assertEquals(
        DATA.withVolatilitySurface(newSurface),
        new BlackOptionDataBundle(F, CURVE, newSurface, DATE));
  }
}
 @Test
 public void testBuilders() {
   final ZonedDateTime newDate = DATE.plusDays(1);
   assertEquals(DATA.withDate(newDate), new BlackOptionDataBundle(F, CURVE, SURFACE, newDate));
   final double newForward = F + 1;
   assertEquals(
       DATA.withForward(newForward), new BlackOptionDataBundle(newForward, CURVE, SURFACE, DATE));
   final YieldCurve newCurve = YieldCurve.from(ConstantDoublesCurve.from(0.05));
   assertEquals(
       DATA.withInterestRateCurve(newCurve),
       new BlackOptionDataBundle(F, newCurve, SURFACE, DATE));
   final VolatilitySurface newSurface = new VolatilitySurface(ConstantDoublesSurface.from(0.9));
   assertEquals(
       DATA.withVolatilitySurface(newSurface),
       new BlackOptionDataBundle(F, CURVE, newSurface, DATE));
 }
  @Test
  public void ImpliedTreeEuropeanRecoveryTest() {
    final double interest = 0.06;
    final YieldAndDiscountCurve yCrv = YieldCurve.from(ConstantDoublesCurve.from(interest));
    final double cost = 0.02;
    final double atmVol = 0.47;
    final ZonedDateTime date = DateUtils.getUTCDate(2010, 7, 1);
    final double spot = 100;
    final Function<Double, Double> smile =
        new Function<Double, Double>() {

          @Override
          public Double evaluate(final Double... tk) {
            ArgChecker.isTrue(tk.length == 2);
            final double k = tk[1];
            return atmVol + (spot - k) * 0.0005;
          }
        };

    final StandardOptionDataBundle data =
        new StandardOptionDataBundle(
            yCrv, cost, new VolatilitySurface(FunctionalDoublesSurface.from(smile)), spot, date);

    final double[] strikes = new double[] {spot * 0.9, spot, spot * 1.11};
    final int nSteps = 7;
    final double time = 1.;

    for (int i = 0; i < strikes.length; ++i) {
      final double strike = strikes[i];
      final boolean isCall = strike >= spot ? true : false;
      final OptionFunctionProvider1D function =
          new EuropeanVanillaOptionFunctionProvider(strike, time, nSteps, isCall);
      final double tree = _modelTrinomial.getPrice(function, data);
      final double black =
          BlackScholesFormulaRepository.price(
              spot, strike * 0.9, time, data.getVolatility(time, strike), interest, cost, isCall);
      assertEquals(tree, black, black * 0.2);
    }

    try {
      _model.getPrice(
          new EuropeanVanillaOptionFunctionProvider(strikes[2], time, nSteps, true), data);
      throw new RuntimeException();
    } catch (Exception e) {
      assertTrue(e instanceof IllegalArgumentException);
    }
  }
/** Test. */
@Test(groups = TestGroup.UNIT)
public class LogOptionModelTest {
  private static final AnalyticOptionModel<LogOptionDefinition, StandardOptionDataBundle> MODEL =
      new LogOptionModel();
  private static final Set<Greek> REQUIRED_GREEKS = Collections.singleton(Greek.FAIR_PRICE);
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2009, 1, 1);
  private static final Expiry EXPIRY =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.75));
  private static final YieldAndDiscountCurve CURVE =
      YieldCurve.from(ConstantDoublesCurve.from(0.08));
  private static final double B = 0.04;
  private static final double SPOT = 100;
  private static final double EPS = 1e-4;

  @Test
  public void test() {
    LogOptionDefinition definition = getDefinition(70);
    assertPriceEquals(definition, 0.2, 0.3510);
    assertPriceEquals(definition, 0.3, 0.3422);
    assertPriceEquals(definition, 0.4, 0.3379);
    assertPriceEquals(definition, 0.5, 0.3365);
    assertPriceEquals(definition, 0.6, 0.3362);
    definition = getDefinition(130);
    assertPriceEquals(definition, 0.2, 0.0056);
    assertPriceEquals(definition, 0.3, 0.0195);
    assertPriceEquals(definition, 0.4, 0.0363);
    assertPriceEquals(definition, 0.5, 0.0532);
    assertPriceEquals(definition, 0.6, 0.0691);
  }

  private void assertPriceEquals(
      final LogOptionDefinition definition, final double sigma, final double price) {
    final StandardOptionDataBundle bundle = getBundle(sigma);
    final GreekResultCollection actual = MODEL.getGreeks(definition, bundle, REQUIRED_GREEKS);
    assertEquals(actual.get(Greek.FAIR_PRICE), price, EPS);
  }

  private StandardOptionDataBundle getBundle(final double sigma) {
    return new StandardOptionDataBundle(
        CURVE, B, new VolatilitySurface(ConstantDoublesSurface.from(sigma)), SPOT, DATE);
  }

  private LogOptionDefinition getDefinition(final double strike) {
    return new LogOptionDefinition(strike, EXPIRY);
  }
}
public class RelativeOutperformanceOptionModelTest {
  private static final double S1 = 130;
  private static final double S2 = 100;
  private static final YieldAndDiscountCurve R = YieldCurve.from(ConstantDoublesCurve.from(0.07));
  private static final double B1 = 0.05;
  private static final double B2 = 0.03;
  private static final VolatilitySurface SIGMA1 =
      new VolatilitySurface(ConstantDoublesSurface.from(0.3));
  private static final VolatilitySurface SIGMA2 =
      new VolatilitySurface(ConstantDoublesSurface.from(0.4));
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 7, 1);
  private static final RelativeOutperformanceOptionModel MODEL =
      new RelativeOutperformanceOptionModel();
  private static final Expiry EXPIRY =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.25));
  private static final double EPS = 1e-4;

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDefinition() {
    MODEL.getPricingFunction(null);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    MODEL
        .getPricingFunction(new RelativeOutperformanceOptionDefinition(0.1, EXPIRY, true))
        .evaluate((StandardTwoAssetOptionDataBundle) null);
  }

  @Test
  public void test() {
    RelativeOutperformanceOptionDefinition option =
        new RelativeOutperformanceOptionDefinition(0.1, EXPIRY, true);
    StandardTwoAssetOptionDataBundle data =
        new StandardTwoAssetOptionDataBundle(R, B1, B2, SIGMA1, SIGMA2, S1, S2, -0.5, DATE);
    assertEquals(MODEL.getPricingFunction(option).evaluate(data), 1.2582, EPS);
    option = new RelativeOutperformanceOptionDefinition(0.5, EXPIRY, true);
    data = data.withCorrelation(0);
    assertEquals(MODEL.getPricingFunction(option).evaluate(data), 0.8449, EPS);
    option = new RelativeOutperformanceOptionDefinition(1, EXPIRY, true);
    data = data.withCorrelation(0.5);
    assertEquals(MODEL.getPricingFunction(option).evaluate(data), 0.3382, EPS);
  }
}
Example #10
0
  public void testFlatSurface() {
    final double theta = 0.5;
    final double ft = FORWARD_CURVE.getForward(EXPIRY);

    final double fL = Math.log(ft / 5.0);
    final double fH = Math.log(5.0 * ft);
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, fL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, fH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 100, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(fL, fH, 101, 0.0);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db =
        new PDE1DDataBundle<>(PDE, INITIAL_COND, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final int n = res.getNumberSpaceNodes();

    final double kVol = Math.sqrt(-2 * (res.getFunctionValue(n / 2) - Math.log(ft)) / EXPIRY);

    assertEquals(FLAT_VOL, kVol, 1e-6);

    final YieldAndDiscountCurve yieldCurve =
        new YieldCurve("test", ConstantDoublesCurve.from(DRIFT));
    final AffineDividends ad = AffineDividends.noDividends();

    final EquityVarianceSwapBackwardsPurePDE backSolver = new EquityVarianceSwapBackwardsPurePDE();
    final PureLocalVolatilitySurface plv =
        new PureLocalVolatilitySurface(ConstantDoublesSurface.from(FLAT_VOL));

    final double[] res2 = backSolver.expectedVariance(SPOT, yieldCurve, ad, EXPIRY, plv);
    final double kVol2 = Math.sqrt(res2[0] / EXPIRY);
    assertEquals(FLAT_VOL, kVol2, 1e-6);
  }
public class ComplexChooserOptionModelTest {
  private static final YieldAndDiscountCurve CURVE =
      YieldCurve.from(ConstantDoublesCurve.from(0.1));
  private static final double B = 0.05;
  private static final VolatilitySurface SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(0.35));
  private static final double SPOT = 50;
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 7, 1);
  private static final double CHOOSE_TIME = 0.25;
  private static final double CALL_LIFE = 0.5;
  private static final double PUT_LIFE = 7. / 12;
  private static final Expiry CHOOSE_DATE =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, CHOOSE_TIME));
  private static final Expiry CALL_EXPIRY =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, CALL_LIFE));
  private static final Expiry PUT_EXPIRY =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, PUT_LIFE));
  private static final double CALL_STRIKE = 55;
  private static final double PUT_STRIKE = 48;

  @SuppressWarnings("unused")
  private static final OptionDefinition CALL =
      new EuropeanVanillaOptionDefinition(CALL_STRIKE, CALL_EXPIRY, true);

  @SuppressWarnings("unused")
  private static final OptionDefinition PUT =
      new EuropeanVanillaOptionDefinition(PUT_STRIKE, PUT_EXPIRY, false);

  private static final ComplexChooserOptionDefinition CHOOSER =
      new ComplexChooserOptionDefinition(
          CHOOSE_DATE, CALL_STRIKE, CALL_EXPIRY, PUT_STRIKE, PUT_EXPIRY);
  private static final StandardOptionDataBundle DATA =
      new StandardOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE);
  private static final AnalyticOptionModel<ComplexChooserOptionDefinition, StandardOptionDataBundle>
      MODEL = new ComplexChooserOptionModel();

  @SuppressWarnings("unused")
  private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM =
      new BlackScholesMertonModel();

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDefinition() {
    MODEL.getPricingFunction(null);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    MODEL.getPricingFunction(CHOOSER).evaluate((StandardOptionDataBundle) null);
  }

  @Test
  public void test() {
    // TODO test wrt BSM
    // final double spot = 2;
    // final StandardOptionDataBundle data = DATA.withSpot(spot);
    // final ComplexChooserOptionDefinition chooser = new ComplexChooserOptionDefinition(new
    // Expiry(DATE), CALL_STRIKE, CALL_EXPIRY, PUT_STRIKE, PUT_EXPIRY);
    // assertEquals(MODEL.getPricingFunction(chooser).evaluate(data), BSM.getPricingFunction(
    // new EuropeanVanillaOptionDefinition(CALL_STRIKE, new
    // Expiry(DateUtil.getDateOffsetWithYearFraction(DATE, CALL_LIFE)), true)).evaluate(data), 0);
    assertEquals(MODEL.getPricingFunction(CHOOSER).evaluate(DATA), 6.0508, 1e-4);
  }
}
/** Test. */
@Test
public class SkewKurtosisOptionDataBundleTest {
  private static final double R = 0.03;
  private static final double SIGMA = 0.25;
  private static final YieldAndDiscountCurve CURVE = YieldCurve.from(ConstantDoublesCurve.from(R));
  private static final double B = 0.03;
  private static final VolatilitySurface SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(SIGMA));
  private static final double SPOT = 100;
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 5, 1);
  private static final double SKEW = 1.2;
  private static final double KURTOSIS = 4.5;
  private static final YieldAndDiscountCurve OTHER_CURVE =
      YieldCurve.from(ConstantDoublesCurve.from(R + 1));
  private static final double OTHER_B = B + 1;
  private static final VolatilitySurface OTHER_SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(SIGMA + 1));
  private static final double OTHER_SPOT = SPOT + 1;
  private static final ZonedDateTime OTHER_DATE = DateUtils.getDateOffsetWithYearFraction(DATE, 1);
  private static final double OTHER_SKEW = 0.1;
  private static final double OTHER_KURTOSIS = 3;
  private static final SkewKurtosisOptionDataBundle DATA =
      new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, SKEW, KURTOSIS);

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullBundle() {
    new SkewKurtosisOptionDataBundle(null);
  }

  @Test
  public void testGetters() {
    assertEquals(DATA.getInterestRateCurve(), CURVE);
    assertEquals(DATA.getCostOfCarry(), B, 0);
    assertEquals(DATA.getDate(), DATE);
    assertEquals(DATA.getSpot(), SPOT, 0);
    assertEquals(DATA.getVolatilitySurface(), SURFACE);
  }

  @Test
  public void testGetInterestRate() {
    for (int i = 0; i < 10; i++) {
      assertEquals(DATA.getInterestRate(Math.random()), R, 1e-15);
    }
  }

  @Test
  public void testGetVolatility() {
    for (int i = 0; i < 10; i++) {
      assertEquals(DATA.getVolatility(Math.random(), Math.random()), SIGMA, 1e-15);
    }
  }

  @Test
  public void testEqualsAndHashCode() {
    final SkewKurtosisOptionDataBundle data1 =
        new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, SKEW, KURTOSIS);
    final SkewKurtosisOptionDataBundle data2 =
        new SkewKurtosisOptionDataBundle(
            new StandardOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE), SKEW, KURTOSIS);
    final SkewKurtosisOptionDataBundle data3 =
        new SkewKurtosisOptionDataBundle(
            new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, SKEW, KURTOSIS));
    assertEquals(DATA, data1);
    assertEquals(DATA.hashCode(), data1.hashCode());
    assertEquals(DATA, data2);
    assertEquals(DATA.hashCode(), data2.hashCode());
    assertEquals(DATA, data3);
    assertEquals(DATA.hashCode(), data3.hashCode());
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(OTHER_CURVE, B, SURFACE, SPOT, DATE, SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, OTHER_B, SURFACE, SPOT, DATE, SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, B, OTHER_SURFACE, SPOT, DATE, SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, OTHER_SPOT, DATE, SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, OTHER_DATE, SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, OTHER_SKEW, KURTOSIS)));
    assertFalse(
        DATA.equals(
            new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, SKEW, OTHER_KURTOSIS)));
  }

  @Test
  public void testBuilders() {
    assertEquals(
        new SkewKurtosisOptionDataBundle(OTHER_CURVE, B, SURFACE, SPOT, DATE, SKEW, KURTOSIS),
        DATA.withInterestRateCurve(OTHER_CURVE));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, OTHER_B, SURFACE, SPOT, DATE, SKEW, KURTOSIS),
        DATA.withCostOfCarry(OTHER_B));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, B, OTHER_SURFACE, SPOT, DATE, SKEW, KURTOSIS),
        DATA.withVolatilitySurface(OTHER_SURFACE));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, OTHER_SPOT, DATE, SKEW, KURTOSIS),
        DATA.withSpot(OTHER_SPOT));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, OTHER_DATE, SKEW, KURTOSIS),
        DATA.withDate(OTHER_DATE));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, OTHER_SKEW, KURTOSIS),
        DATA.withSkew(OTHER_SKEW));
    assertEquals(
        new SkewKurtosisOptionDataBundle(CURVE, B, SURFACE, SPOT, DATE, SKEW, OTHER_KURTOSIS),
        DATA.withKurtosis(OTHER_KURTOSIS));
  }
}
/** Test. */
@Test(groups = TestGroup.UNIT)
public class LogPayoffWithDividendsTest {

  private static final PDE1DCoefficientsProvider PDE_PROVIDER = new PDE1DCoefficientsProvider();
  private static final InitialConditionsProvider INITIAL_COND_PROVIDER =
      new InitialConditionsProvider();

  private static final Interpolator1D INTEPOLATOR1D =
      Interpolator1DFactory.DOUBLE_QUADRATIC_INSTANCE;

  private static final double EXPIRY = 1.5;
  private static final double DIVIDEND_DATE = 0.85;
  private static final double ALPHA = 6.0;
  private static final double BETA = 0.04;
  private static final double PURE_VOL = 0.5;
  private static final double VOL = 0.4;
  private static final double SPOT = 100.0;
  private static final double DRIFT = 0.1; // 0.1;
  private static final YieldAndDiscountCurve DISCOUNT_CURVE =
      new YieldCurve("yield curve", ConstantDoublesCurve.from(DRIFT));
  private static final AffineDividends DIVIDENDS =
      new AffineDividends(new double[] {DIVIDEND_DATE}, new double[] {ALPHA}, new double[] {BETA});
  private static final EquityDividendsCurvesBundle DIV_CURVES =
      new EquityDividendsCurvesBundle(SPOT, DISCOUNT_CURVE, DIVIDENDS);
  private static final LocalVolatilitySurfaceMoneyness PURE_LOCAL_VOL_FLAT;
  private static final LocalVolatilitySurfaceStrike LOCAL_VOL;
  private static final LocalVolatilitySurfaceStrike LOCAL_VOL_SPECIAL;
  private static final LocalVolatilitySurfaceStrike LOCAL_VOL_FLAT;
  private static final LocalVolatilitySurfaceMoneyness PURE_LOCAL_VOL;

  private static final Function1D<Double, Double> PURE_LOG_PAY_OFF;

  static {
    PURE_LOG_PAY_OFF =
        new Function1D<Double, Double>() {
          final double fT = DIV_CURVES.getF(EXPIRY);
          final double dT = DIV_CURVES.getD(EXPIRY);

          @Override
          public Double evaluate(final Double x) {
            final double s = (fT - dT) * Math.exp(x) + dT;
            return Math.log(s);
          }
        };

    final Function<Double, Double> localVol =
        new Function<Double, Double>() {
          @Override
          public Double evaluate(final Double... ts) {
            final double t = ts[0];
            final double s = ts[1];
            final double d = DIV_CURVES.getD(t);
            if (s < d) {
              return 0.0;
            }
            return PURE_VOL * (s - d) / s;
          }
        };

    LOCAL_VOL = new LocalVolatilitySurfaceStrike(FunctionalDoublesSurface.from(localVol));

    final Function<Double, Double> localVolSpecial =
        new Function<Double, Double>() {
          @Override
          public Double evaluate(final Double... tf) {
            final double t = tf[0];
            final double f = tf[1];
            final double rtT = DIV_CURVES.getR(t);
            final double dtT = DIV_CURVES.getD(t);
            final double ftT = DIV_CURVES.getF(t);
            //        if (f < d) {
            //          return 0.0;
            //        }
            final double x = f / rtT / (ftT - dtT);
            return PURE_LOCAL_VOL.getVolatility(t, x);
          }
        };

    LOCAL_VOL_SPECIAL =
        new LocalVolatilitySurfaceStrike(FunctionalDoublesSurface.from(localVolSpecial));

    final Function<Double, Double> pureLocalVol =
        new Function<Double, Double>() {
          @Override
          public Double evaluate(final Double... tx) {
            final double t = tx[0];
            final double x = tx[1];
            final double f = DIV_CURVES.getF(t);
            final double d = DIV_CURVES.getD(t);
            return VOL * ((f - d) * x + d) / (f - d) / x;
          }
        };

    PURE_LOCAL_VOL =
        new LocalVolatilitySurfaceMoneyness(
            FunctionalDoublesSurface.from(pureLocalVol), new ForwardCurve(1.0));

    PURE_LOCAL_VOL_FLAT =
        new LocalVolatilitySurfaceMoneyness(
            ConstantDoublesSurface.from(PURE_VOL), new ForwardCurve(1.0));
    LOCAL_VOL_FLAT = new LocalVolatilitySurfaceStrike(ConstantDoublesSurface.from(VOL));
  }

  /**
   * Check the the log-contract is correctly prices using a backwards PDE expressed in terms of (the
   * log of) the 'pure' stock price - this avoids having jumps conditions in the PDE. The pure local
   * volatility surface is flat.
   */
  @Test
  public void backwardsLogPureSpotPDEtest() {
    final double fT = DIV_CURVES.getF(EXPIRY);
    final double lnFT = Math.log(fT);
    final double val = logContactPriceFromPureSpot(PURE_LOCAL_VOL_FLAT);
    assertEquals(PURE_VOL, Math.sqrt(-2 * (val - lnFT) / EXPIRY), 1e-6);
    //   System.out.println(val + "\t" + Math.sqrt(-2 * (val - lnFT) / EXPIRY));
  }

  /**
   * Check the the log-contract is correctly prices using a backwards PDE expressed in terms of (the
   * log of) the real stock price - this requires having jumps conditions in the PDE. The local
   * volatility surface is derived from the flat pure local volatility surface.
   */
  @Test
  public void backwardsLogSpotPDEtest() {
    final double fT = DIV_CURVES.getF(EXPIRY);
    final double lnFT = Math.log(fT);
    final double val = logContractPriceFromSpotPDE(LOCAL_VOL);
    assertEquals(PURE_VOL, Math.sqrt(-2 * (val - lnFT) / EXPIRY), 1e-4);
    //   System.out.println(val + "\t" + Math.sqrt(-2 * (val - lnFT) / EXPIRY));
  }

  /**
   * Price the log-contact using the PDE in spot (with the jump conditions) with a flat local
   * volatility surface, and the PDE in pure spot using the pure local volatility surface derived
   * from the flat surface. They MUST give the same answer
   */
  @Test
  public void backwardsPDETest() {
    final double fT = DIV_CURVES.getF(EXPIRY);
    final double lnFT = Math.log(fT);
    final double val1 = logContractPriceFromSpotPDE(LOCAL_VOL_FLAT);
    final double val2 = logContactPriceFromPureSpot(PURE_LOCAL_VOL);
    // convert to realised vol
    final double vol1 = Math.sqrt(-2 * (val1 - lnFT) / EXPIRY);
    final double vol2 = Math.sqrt(-2 * (val2 - lnFT) / EXPIRY);
    assertEquals(vol1, vol2, 1e-3);
    //   System.out.println(vol1 + "\t" + vol2);
  }

  /**
   * Check the the log-contract is correctly prices using a backwards PDE expressed in terms of (the
   * log of) the forward F(t,T) - this requires NO jumps conditions in the PDE
   */
  @Test
  public void backwardsDebugPDEtest() {
    final double fT = DIV_CURVES.getF(EXPIRY);
    final double lnFT = Math.log(fT);

    final Function1D<Double, Double> payoff =
        new Function1D<Double, Double>() {

          @Override
          public Double evaluate(final Double y) {
            return y - lnFT;
          }
        };

    // ZZConvectionDiffusionPDEDataBundle pdeBundle1 = getBackwardsPDEDataBundle(EXPIRY, LOCAL_VOL,
    // payoff);
    // ConvectionDiffusionPDE1DCoefficients pde =
    // PDE_PROVIDER.getLogBackwardsLocalVol(FORWARD_CURVE, EXPIRY, LOCAL_VOL);
    final ConvectionDiffusionPDE1DCoefficients pde =
        PDE_PROVIDER.getLogBackwardsLocalVol(0.0, 0.0, EXPIRY, LOCAL_VOL_SPECIAL);

    final double theta = 0.5;
    final double range = Math.log(5);
    final double yL = lnFT - range;
    final double yH = lnFT + range;
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, yL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, yH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 100, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final double[] sNodes = grid.getSpaceNodes();

    // run the PDE solver backward to the dividend date
    // PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db1 = new
    // PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, initialCon, lower1, upper1,
    // grid1);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db1 =
        new PDE1DDataBundle<>(pde, payoff, lower, upper, grid);
    final PDETerminalResults1D res = (PDETerminalResults1D) solver.solve(db1);

    final Interpolator1DDataBundle interpolDB =
        INTEPOLATOR1D.getDataBundle(sNodes, res.getTerminalResults());

    final double val = INTEPOLATOR1D.interpolate(interpolDB, lnFT);
    assertEquals(0.41491529, Math.sqrt(-2 * (val) / EXPIRY), 5e-4); // Number from backwardsPDETest
    //   System.out.println(val + "\t" + Math.sqrt(-2 * val / EXPIRY));
  }

  private double logContactPriceFromPureSpot(final LocalVolatilitySurfaceMoneyness lv) {

    final double fT = DIV_CURVES.getF(EXPIRY);
    final double dT = DIV_CURVES.getD(EXPIRY);

    final double dStar = dT / (fT - dT);

    final ConvectionDiffusionPDE1DCoefficients pde =
        PDE_PROVIDER.getLogBackwardsLocalVol(EXPIRY, lv);

    final double theta = 0.5;
    final double yL = -0.5;
    final double yH = 0.5;
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower =
        new NeumannBoundaryCondition(1 / (1 + dStar * Math.exp(-yL)), yL, true);
    final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, yH, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, EXPIRY, 100, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0);

    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db =
        new PDE1DDataBundle<>(pde, PURE_LOG_PAY_OFF, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final int n = res.getNumberSpaceNodes();

    final double val = res.getFunctionValue(n / 2);
    return val;
  }

  /**
   * Prices a log-contract for a given local volatility surface by backwards solving the PDE
   * expressed in terms of (the log of) the real stock price - this requires having jumps conditions
   * in the PDE
   *
   * @param lv Local volatility
   * @return Forward (non-discounted) price of log-contact
   */
  private double logContractPriceFromSpotPDE(final LocalVolatilitySurfaceStrike lv) {

    // Set up the PDE to give the forward (non-discounted) option price
    final ConvectionDiffusionPDE1DCoefficients pde =
        PDE_PROVIDER.getLogBackwardsLocalVol(0.0, -DRIFT, EXPIRY, lv);
    final Function1D<Double, Double> initialCon =
        INITIAL_COND_PROVIDER.getLogContractPayoffInLogCoordinate();

    final double theta = 0.5;
    final double yL = Math.log(SPOT / 6);
    final double yH = Math.log(6 * SPOT);
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower1 = new NeumannBoundaryCondition(1.0, yL, true);
    final BoundaryCondition upper1 = new NeumannBoundaryCondition(1.0, yH, false);

    final MeshingFunction timeMesh1 =
        new ExponentialMeshing(0, EXPIRY - DIVIDEND_DATE - 1e-6, 50, 0.0);
    final MeshingFunction timeMesh2 =
        new ExponentialMeshing(EXPIRY - DIVIDEND_DATE + 1e-6, EXPIRY, 50, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0);

    final PDEGrid1D grid1 = new PDEGrid1D(timeMesh1, spaceMesh);
    final double[] sNodes1 = grid1.getSpaceNodes();

    // run the PDE solver backward to the dividend date
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db1 =
        new PDE1DDataBundle<>(pde, initialCon, lower1, upper1, grid1);
    final PDETerminalResults1D res1 = (PDETerminalResults1D) solver.solve(db1);

    // Map the spot nodes after (in calendar time) the dividend payment to nodes before
    final int nSNodes = sNodes1.length;
    final double[] sNodes2 = new double[nSNodes];
    final double lnBeta = Math.log(1 - BETA);
    for (int i = 0; i < nSNodes; i++) {
      final double temp = sNodes1[i];
      if (temp < 0) {
        sNodes2[i] = Math.log(Math.exp(temp) + ALPHA) - lnBeta;
      } else {
        sNodes2[i] = temp + Math.log(1 + ALPHA * Math.exp(-temp)) - lnBeta;
      }
    }

    final PDEGrid1D grid2 = new PDEGrid1D(timeMesh2.getPoints(), sNodes2);
    final BoundaryCondition lower2 = new NeumannBoundaryCondition(1.0, sNodes2[0], true);
    final BoundaryCondition upper2 = new NeumannBoundaryCondition(1.0, sNodes2[nSNodes - 1], false);

    // run the PDE solver backward from the dividend date to zero
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db2 =
        new PDE1DDataBundle<>(pde, res1.getTerminalResults(), lower2, upper2, grid2);
    final PDETerminalResults1D res2 = (PDETerminalResults1D) solver.solve(db2);

    final Interpolator1DDataBundle interpolDB2 =
        INTEPOLATOR1D.getDataBundle(sNodes2, res2.getTerminalResults());
    final double val2 = INTEPOLATOR1D.interpolate(interpolDB2, Math.log(SPOT));
    return val2;
  }
}
public class PoweredOptionModelTest {
  private static final AnalyticOptionModel<PoweredOptionDefinition, StandardOptionDataBundle>
      POWERED_MODEL = new PoweredOptionModel();
  private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM =
      new BlackScholesMertonModel();
  private static final Set<Greek> REQUIRED_GREEKS = Collections.singleton(Greek.FAIR_PRICE);
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2009, 1, 1);
  private static final Expiry EXPIRY =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.5));
  private static final YieldAndDiscountCurve CURVE = new YieldCurve(ConstantDoublesCurve.from(0.1));
  private static final double B = 0.07;
  private static final double SPOT = 100;
  private static final double STRIKE = 100;
  private static final double SMALL_EPS = 1e-9;
  private static final double BIG_EPS = 1e-4;

  @Test
  public void testNonIntegerPower() {
    try {
      POWERED_MODEL.getGreeks(
          new PoweredOptionDefinition(100, EXPIRY, 1.3, true), getBundle(0.), REQUIRED_GREEKS);
      Assert.fail();
    } catch (final OptionPricingException e) {
      // Expected
    }
  }

  @Test
  public void testPowerOfOne() {
    PoweredOptionDefinition poweredDefinition = getPoweredDefinition(1, true);
    EuropeanVanillaOptionDefinition vanillaDefinition = getVanillaOption(true);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.1);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.2);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.3);
    poweredDefinition = getPoweredDefinition(1, false);
    vanillaDefinition = getVanillaOption(false);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.1);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.2);
    assertPriceEquals(poweredDefinition, vanillaDefinition, 0.3);
  }

  @Test
  public void test() {
    PoweredOptionDefinition definition = getPoweredDefinition(2, true);
    assertPriceEquals(definition, 0.1, 53.4487);
    assertPriceEquals(definition, 0.2, 160.2944);
    assertPriceEquals(definition, 0.3, 339.3713);
    definition = getPoweredDefinition(3, true);
    assertPriceEquals(definition, 0.1, 758.8427);
    assertPriceEquals(definition, 0.2, 4608.7213);
    assertPriceEquals(definition, 0.3, 15624.1041);
    definition = getPoweredDefinition(2, false);
    assertPriceEquals(definition, 0.1, 9.7580);
    assertPriceEquals(definition, 0.2, 57.8677);
    assertPriceEquals(definition, 0.3, 142.2726);
    definition = getPoweredDefinition(3, false);
    assertPriceEquals(definition, 0.1, 89.6287);
    assertPriceEquals(definition, 0.2, 1061.2120);
    assertPriceEquals(definition, 0.3, 3745.1853);
  }

  private void assertPriceEquals(
      final PoweredOptionDefinition poweredDefinition,
      final EuropeanVanillaOptionDefinition vanillaDefinition,
      final double sigma) {
    final StandardOptionDataBundle bundle = getBundle(sigma);
    final GreekResultCollection actual =
        POWERED_MODEL.getGreeks(poweredDefinition, bundle, REQUIRED_GREEKS);
    final GreekResultCollection expected =
        BSM.getGreeks(vanillaDefinition, bundle, REQUIRED_GREEKS);
    assertEquals(expected.get(Greek.FAIR_PRICE), actual.get(Greek.FAIR_PRICE), SMALL_EPS);
  }

  private void assertPriceEquals(
      final PoweredOptionDefinition poweredDefinition, final double sigma, final double price) {
    final StandardOptionDataBundle bundle = getBundle(sigma);
    final GreekResultCollection actual =
        POWERED_MODEL.getGreeks(poweredDefinition, bundle, REQUIRED_GREEKS);
    assertEquals(price, actual.get(Greek.FAIR_PRICE), BIG_EPS * price);
  }

  private StandardOptionDataBundle getBundle(final double sigma) {
    return new StandardOptionDataBundle(
        CURVE, B, new VolatilitySurface(ConstantDoublesSurface.from(sigma)), SPOT, DATE);
  }

  private PoweredOptionDefinition getPoweredDefinition(final double power, final boolean isCall) {
    return new PoweredOptionDefinition(STRIKE, EXPIRY, power, isCall);
  }

  private EuropeanVanillaOptionDefinition getVanillaOption(final boolean isCall) {
    return new EuropeanVanillaOptionDefinition(STRIKE, EXPIRY, isCall);
  }
}
  @Test
  /**
   * Tests the comparison with the other implementation. This test may be removed when only one
   * version remains.
   */
  public void comparison() {
    final AnalyticOptionModel<EuropeanStandardBarrierOptionDefinition, StandardOptionDataBundle>
        model = new EuropeanStandardBarrierOptionModel();
    final StandardOptionDataBundle data =
        new StandardOptionDataBundle(
            YieldCurve.from(ConstantDoublesCurve.from(RATE_DOM)),
            COST_OF_CARRY,
            new VolatilitySurface(ConstantDoublesSurface.from(VOLATILITY)),
            SPOT,
            REFERENCE_DATE);
    final Expiry expiry = new Expiry(EXPIRY_DATE);

    final double priceDI1 =
        BARRIER_FUNCTION.getPrice(
            VANILLA_CALL_K100, BARRIER_DOWN_IN, REBATE, SPOT, COST_OF_CARRY, RATE_DOM, VOLATILITY);
    final EuropeanStandardBarrierOptionDefinition optionBarrierDI =
        new EuropeanStandardBarrierOptionDefinition(
            STRIKE_MID, expiry, IS_CALL, BARRIER_DOWN_IN, REBATE);
    final double priceDI2 = model.getPricingFunction(optionBarrierDI).evaluate(data);
    assertEquals("Comparison Down In", priceDI2, priceDI1, 1.0E-10);

    final double priceDO1 =
        BARRIER_FUNCTION.getPrice(
            VANILLA_CALL_K100, BARRIER_DOWN_OUT, REBATE, SPOT, COST_OF_CARRY, RATE_DOM, VOLATILITY);
    final EuropeanStandardBarrierOptionDefinition optionBarrierDO =
        new EuropeanStandardBarrierOptionDefinition(
            STRIKE_MID, expiry, IS_CALL, BARRIER_DOWN_OUT, REBATE);
    final double priceDO2 = model.getPricingFunction(optionBarrierDO).evaluate(data);
    assertEquals("Comparison Down Out", priceDO2, priceDO1, 1.0E-10);

    final double priceUI1 =
        BARRIER_FUNCTION.getPrice(
            VANILLA_CALL_K100, BARRIER_UP_IN, REBATE, SPOT, COST_OF_CARRY, RATE_DOM, VOLATILITY);
    final EuropeanStandardBarrierOptionDefinition optionBarrierUI =
        new EuropeanStandardBarrierOptionDefinition(
            STRIKE_MID, expiry, IS_CALL, BARRIER_UP_IN, REBATE);
    final double priceUI2 = model.getPricingFunction(optionBarrierUI).evaluate(data);
    assertEquals("Comparison Up In", priceUI2, priceUI1, 1.0E-10);

    final double priceUO1 =
        BARRIER_FUNCTION.getPrice(
            VANILLA_CALL_K100, BARRIER_UP_OUT, REBATE, SPOT, COST_OF_CARRY, RATE_DOM, VOLATILITY);
    final EuropeanStandardBarrierOptionDefinition optionBarrierUO =
        new EuropeanStandardBarrierOptionDefinition(
            STRIKE_MID, expiry, IS_CALL, BARRIER_UP_OUT, REBATE);
    final double priceUO2 = model.getPricingFunction(optionBarrierUO).evaluate(data);
    assertEquals("Comparison Up Out", priceUO2, priceUO1, 1.0E-10);

    final double vol0 = 0.0;
    final double priceVol01 =
        BARRIER_FUNCTION.getPrice(
            VANILLA_CALL_K100, BARRIER_DOWN_IN, REBATE, SPOT, COST_OF_CARRY, RATE_DOM, vol0);
    final StandardOptionDataBundle data0 =
        new StandardOptionDataBundle(
            YieldCurve.from(ConstantDoublesCurve.from(RATE_DOM)),
            COST_OF_CARRY,
            new VolatilitySurface(ConstantDoublesSurface.from(vol0)),
            SPOT,
            REFERENCE_DATE);
    final double priceVol02 = model.getPricingFunction(optionBarrierDI).evaluate(data0);
    assertEquals(priceVol02, priceVol01, 1.0E-10);
  }
/** Test. */
@Test(groups = TestGroup.UNIT)
public class BlackScholesMertonImpliedVolatilitySurfaceModelTest {
  private static final RandomEngine RANDOM = new MersenneTwister64(MersenneTwister.DEFAULT_SEED);
  private static final BlackScholesMertonImpliedVolatilitySurfaceModel MODEL =
      new BlackScholesMertonImpliedVolatilitySurfaceModel();
  private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM =
      new BlackScholesMertonModel();
  private static final StandardOptionDataBundle DATA =
      new StandardOptionDataBundle(
          YieldCurve.from(ConstantDoublesCurve.from(0.01)),
          0.1,
          new VolatilitySurface(ConstantDoublesSurface.from(0.01)),
          100.,
          DateUtils.getUTCDate(2010, 1, 1));
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2009, 1, 1);
  private static final double EPS = 1e-3;

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullPrices() {
    MODEL.getSurface(null, DATA);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testEmptyPrices() {
    MODEL.getSurface(Collections.<OptionDefinition, Double>emptyMap(), DATA);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    MODEL.getSurface(
        Collections.<OptionDefinition, Double>singletonMap(
            new EuropeanVanillaOptionDefinition(RANDOM.nextDouble(), new Expiry(DATE), true), 2.3),
        null);
  }

  @Test
  public void test() {
    boolean isCall;
    double spot, strike, b, price;
    Expiry expiry;
    YieldAndDiscountCurve curve;
    EuropeanVanillaOptionDefinition definition;
    StandardOptionDataBundle initialData, data;
    double sigma = 0.01;
    for (int i = 0; i < 100; i++) {
      expiry = new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, RANDOM.nextDouble() * 2));
      sigma += 0.03;
      spot = 2 * RANDOM.nextDouble() + 10;
      strike = 2 * RANDOM.nextDouble() + 10;
      curve = YieldCurve.from(ConstantDoublesCurve.from(RANDOM.nextDouble() / 10));
      b = 0; // RANDOM.nextDouble() / 20;
      isCall = RANDOM.nextDouble() < 0.5 ? true : false;
      definition = new EuropeanVanillaOptionDefinition(strike, expiry, isCall);
      initialData = new StandardOptionDataBundle(curve, b, null, spot, DATE);
      data =
          new StandardOptionDataBundle(
              curve, b, new VolatilitySurface(ConstantDoublesSurface.from(sigma)), spot, DATE);
      price = BSM.getPricingFunction(definition).evaluate(data);
      assertEquals(
          sigma,
          MODEL
              .getSurface(
                  Collections.<OptionDefinition, Double>singletonMap(definition, price),
                  initialData)
              .getVolatility(DoublesPair.of(0., 0.)),
          EPS);
    }
  }
}
/** Test. */
@Test
public class FFTOptionModelTest {
  private static final HashSet<Greek> GREEKS = Sets.newHashSet(Greek.FAIR_PRICE);
  private static final double R = 0.005;
  private static final YieldCurve YIELD_CURVE = YieldCurve.from(ConstantDoublesCurve.from(R));
  private static final double BLACK_VOL = 0.34;
  private static final VolatilitySurface VOLATILITY_SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(BLACK_VOL));
  private static final double FORWARD = 100;
  private static final double T = 2;
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2011, 1, 1);
  private static final ZonedDateTime MATURITY = DATE.plusYears((int) T);
  private static final Expiry EXPIRY = new Expiry(MATURITY);
  private static final EuropeanVanillaOptionDefinition ITM_CALL =
      new EuropeanVanillaOptionDefinition(99, EXPIRY, true);
  private static final EuropeanVanillaOptionDefinition OTM_CALL =
      new EuropeanVanillaOptionDefinition(101, EXPIRY, true);
  private static final EuropeanVanillaOptionDefinition ITM_PUT =
      new EuropeanVanillaOptionDefinition(101, EXPIRY, false);
  private static final EuropeanVanillaOptionDefinition OTM_PUT =
      new EuropeanVanillaOptionDefinition(99, EXPIRY, false);
  private static final MartingaleCharacteristicExponent GAUSSIAN =
      new GaussianMartingaleCharacteristicExponent(BLACK_VOL);
  private static final StandardOptionDataBundle BSM_DATA =
      new StandardOptionDataBundle(
          YIELD_CURVE, R, VOLATILITY_SURFACE, Math.exp(-R * T) * FORWARD, DATE);
  private static final BlackOptionDataBundle BLACK_DATA =
      new BlackOptionDataBundle(FORWARD, YIELD_CURVE, VOLATILITY_SURFACE, DATE);
  private static final OptionModel<EuropeanVanillaOptionDefinition, BlackOptionDataBundle>
      FFT_MODEL = new FFTOptionModel(GAUSSIAN);
  private static final OptionModel<OptionDefinition, StandardOptionDataBundle> BSM_MODEL =
      new BlackScholesMertonModel();
  private static final double EPS = 1e-2;

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullCharacteristicExponent1() {
    new FFTOptionModel(null);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullCharacteristicExponent2() {
    new FFTOptionModel(null, 100, 10, -0.5, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNegativeNStrikes() {
    new FFTOptionModel(GAUSSIAN, -100, 10, -0.5, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNegativeDelta() {
    new FFTOptionModel(GAUSSIAN, 100, -10, -0.5, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testZeroAlpha() {
    new FFTOptionModel(GAUSSIAN, 100, 10, 0, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testAlpha() {
    new FFTOptionModel(GAUSSIAN, 100, 10, -1, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNegativeTolerance() {
    new FFTOptionModel(GAUSSIAN, 100, 10, -1, 1e-8);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDefinition() {
    FFT_MODEL.getGreeks(null, BLACK_DATA, GREEKS);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    FFT_MODEL.getGreeks(ITM_CALL, null, GREEKS);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullGreeks() {
    FFT_MODEL.getGreeks(ITM_CALL, BLACK_DATA, null);
  }

  @Test(expectedExceptions = UnsupportedOperationException.class)
  public void testWrongGreeks() {
    FFT_MODEL.getGreeks(ITM_CALL, BLACK_DATA, Sets.newHashSet(Greek.DELTA, Greek.GAMMA));
  }

  @Test
  public void testPricing() {
    GreekResultCollection fftPrice = FFT_MODEL.getGreeks(ITM_CALL, BLACK_DATA, GREEKS);
    GreekResultCollection bsmPrice = BSM_MODEL.getGreeks(ITM_CALL, BSM_DATA, GREEKS);
    assertEquals(fftPrice.size(), 1);
    assertEquals(fftPrice.get(Greek.FAIR_PRICE), bsmPrice.get(Greek.FAIR_PRICE), EPS);
    fftPrice = FFT_MODEL.getGreeks(OTM_CALL, BLACK_DATA, GREEKS);
    bsmPrice = BSM_MODEL.getGreeks(OTM_CALL, BSM_DATA, GREEKS);
    assertEquals(fftPrice.size(), 1);
    assertEquals(fftPrice.get(Greek.FAIR_PRICE), bsmPrice.get(Greek.FAIR_PRICE), EPS);
    fftPrice = FFT_MODEL.getGreeks(OTM_PUT, BLACK_DATA, GREEKS);
    bsmPrice = BSM_MODEL.getGreeks(OTM_PUT, BSM_DATA, GREEKS);
    assertEquals(fftPrice.size(), 1);
    assertEquals(fftPrice.get(Greek.FAIR_PRICE), bsmPrice.get(Greek.FAIR_PRICE), EPS);
    fftPrice = FFT_MODEL.getGreeks(ITM_PUT, BLACK_DATA, GREEKS);
    bsmPrice = BSM_MODEL.getGreeks(ITM_PUT, BSM_DATA, GREEKS);
    assertEquals(fftPrice.size(), 1);
    assertEquals(fftPrice.get(Greek.FAIR_PRICE), bsmPrice.get(Greek.FAIR_PRICE), EPS);
  }
}
/** Test. */
@Test(groups = TestGroup.UNIT)
public class ExtremeSpreadOptionDefinitionTest {
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2010, 7, 1);
  private static final Expiry EXPIRY = new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 1));
  private static final Expiry PERIOD_END =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.275));
  private static final ExtremeSpreadOptionDefinition PUT =
      new ExtremeSpreadOptionDefinition(EXPIRY, false, PERIOD_END, false);
  private static final ExtremeSpreadOptionDefinition PUT_REVERSE =
      new ExtremeSpreadOptionDefinition(EXPIRY, false, PERIOD_END, true);
  private static final ExtremeSpreadOptionDefinition CALL =
      new ExtremeSpreadOptionDefinition(EXPIRY, true, PERIOD_END, false);
  private static final ExtremeSpreadOptionDefinition CALL_REVERSE =
      new ExtremeSpreadOptionDefinition(EXPIRY, true, PERIOD_END, true);
  private static final DoubleTimeSeries<?> SPOT_SERIES =
      ImmutableZonedDateTimeDoubleTimeSeries.ofUTC(
          new ZonedDateTime[] {
            DateUtils.getUTCDate(2010, 7, 1),
            DateUtils.getUTCDate(2010, 8, 1),
            DateUtils.getUTCDate(2010, 9, 1),
            DateUtils.getUTCDate(2010, 10, 1),
            DateUtils.getUTCDate(2010, 11, 1),
            DateUtils.getUTCDate(2010, 12, 1),
            DateUtils.getUTCDate(2011, 1, 1),
            DateUtils.getUTCDate(2011, 2, 1),
            DateUtils.getUTCDate(2011, 3, 1),
            DateUtils.getUTCDate(2011, 4, 1),
            DateUtils.getUTCDate(2011, 5, 1),
            DateUtils.getUTCDate(2011, 6, 1)
          },
          new double[] {1, 2, 0, 1, 4, 15, 4, 4, 0, 4, 4, 4});
  private static final StandardOptionWithSpotTimeSeriesDataBundle DATA =
      new StandardOptionWithSpotTimeSeriesDataBundle(
          YieldCurve.from(ConstantDoublesCurve.from(0.)),
          0,
          new VolatilitySurface(ConstantDoublesSurface.from(0)),
          2,
          DATE,
          SPOT_SERIES);

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullPeriodEnd() {
    new ExtremeSpreadOptionDefinition(EXPIRY, true, null, true);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testPeriodEndAfterExpiry() {
    new ExtremeSpreadOptionDefinition(
        EXPIRY, false, new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 2)), false);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDate() {
    PUT.getTimeFromPeriodEnd(null);
  }

  @Test
  public void test() {
    assertEquals(PUT.getPeriodEnd(), PERIOD_END);
    assertFalse(PUT.isReverse());
    ExtremeSpreadOptionDefinition other =
        new ExtremeSpreadOptionDefinition(EXPIRY, false, PERIOD_END, false);
    assertEquals(other, PUT);
    assertEquals(other.hashCode(), PUT.hashCode());
    other =
        new ExtremeSpreadOptionDefinition(
            EXPIRY, false, new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.15)), false);
    assertFalse(other.equals(PUT));
    other = new ExtremeSpreadOptionDefinition(EXPIRY, false, PERIOD_END, true);
    assertFalse(other.equals(PUT));
    assertEquals(PUT.getTimeFromPeriodEnd(EXPIRY.getExpiry()), 0.725, 0);
    assertEquals(PUT.getTimeFromPeriodEnd(DATE), -0.275, 0);
  }

  @Test
  public void testExercise() {
    assertFalse(PUT.getExerciseFunction().shouldExercise(DATA, null));
    assertFalse(PUT_REVERSE.getExerciseFunction().shouldExercise(DATA, null));
  }

  @Test
  public void testPayoff() {
    assertEquals(CALL.getPayoffFunction().getPayoff(DATA, null), 13, 0);
    assertEquals(CALL_REVERSE.getPayoffFunction().getPayoff(DATA, null), 0, 0);
    assertEquals(PUT.getPayoffFunction().getPayoff(DATA, null), 0, 0);
    assertEquals(PUT_REVERSE.getPayoffFunction().getPayoff(DATA, null), 13, 0);
  }
}
/** Test. */
@Test
public class BatesGeneralizedJumpDiffusionModelTest {
  private static final AnalyticOptionModel<
          OptionDefinition, BatesGeneralizedJumpDiffusionModelDataBundle>
      MODEL = new BatesGeneralizedJumpDiffusionModel();
  private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM =
      new BlackScholesMertonModel();
  private static final YieldAndDiscountCurve CURVE =
      YieldCurve.from(ConstantDoublesCurve.from(0.08));
  private static final double B = 0.08;
  private static final VolatilitySurface SURFACE =
      new VolatilitySurface(ConstantDoublesSurface.from(0.25));
  private static final double SPOT = 100;
  private static final ZonedDateTime DATE = DateUtils.getUTCDate(2009, 1, 1);
  private static final Expiry EXPIRY1 =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.1));
  private static final Expiry EXPIRY2 =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.25));
  private static final Expiry EXPIRY3 =
      new Expiry(DateUtils.getDateOffsetWithYearFraction(DATE, 0.5));
  private static final double EPS1 = 1e-2;
  private static final double EPS2 = 1e-9;

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullDefinition() {
    MODEL.getPricingFunction(null);
  }

  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testNullData() {
    MODEL
        .getPricingFunction(new EuropeanVanillaOptionDefinition(100, EXPIRY1, true))
        .evaluate((BatesGeneralizedJumpDiffusionModelDataBundle) null);
  }

  @Test
  public void test() {
    OptionDefinition call = new EuropeanVanillaOptionDefinition(80, EXPIRY1, true);
    BatesGeneralizedJumpDiffusionModelDataBundle data =
        new BatesGeneralizedJumpDiffusionModelDataBundle(
            CURVE, B, SURFACE, SPOT, DATE, 0., -0.04, 0.);
    assertEquals(
        BSM.getPricingFunction(call).evaluate(data),
        MODEL.getPricingFunction(call).evaluate(data),
        EPS2);
    call = new EuropeanVanillaOptionDefinition(80, EXPIRY1, true);
    data = data.withLambda(1.).withDelta(0.1);
    assertEquals(20.67, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(90, EXPIRY2, true);
    data = data.withLambda(5.);
    assertEquals(14.13, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(100, EXPIRY3, true);
    data = data.withLambda(10.);
    assertEquals(13.62, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    data = data.withDelta(0.25);
    data = data.withLambda(1.);
    call = new EuropeanVanillaOptionDefinition(90, EXPIRY1, true);
    assertEquals(11.57, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(100, EXPIRY2, true);
    data = data.withLambda(5.);
    assertEquals(12.25, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(110, EXPIRY3, true);
    data = data.withLambda(10.);
    assertEquals(20.43, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    data = data.withDelta(0.5);
    data = data.withLambda(1.);
    call = new EuropeanVanillaOptionDefinition(100, EXPIRY1, true);
    assertEquals(5.18, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(110, EXPIRY2, true);
    data = data.withLambda(5.);
    assertEquals(16.52, MODEL.getPricingFunction(call).evaluate(data), EPS1);
    call = new EuropeanVanillaOptionDefinition(120, EXPIRY3, true);
    data = data.withLambda(10.);
    assertEquals(37.03, MODEL.getPricingFunction(call).evaluate(data), EPS1);
  }
}