@Test
 public void greekTrinomialTest() {
   final LatticeSpecification[] lattices =
       new LatticeSpecification[] {
         new CoxRossRubinsteinLatticeSpecification(),
         new JarrowRuddLatticeSpecification(),
         new TrigeorgisLatticeSpecification(),
         new TianLatticeSpecification()
       };
   final boolean[] tfSet = new boolean[] {true, false};
   for (final LatticeSpecification lattice : lattices) {
     for (final boolean isCall : tfSet) {
       for (final double strike : STRIKES) {
         for (final double interest : INTERESTS) {
           for (final double vol : VOLS) {
             final int nSteps = 177;
             for (final double dividend : DIVIDENDS) {
               final OptionFunctionProvider1D function =
                   new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
               final GreekResultCollection resDiv =
                   _modelTrinomial.getGreeks(lattice, function, SPOT, vol, interest, dividend);
               final double priceDiv =
                   BlackScholesFormulaRepository.price(
                       SPOT, strike, TIME, vol, interest, interest - dividend, isCall);
               final double refPriceDiv = Math.max(Math.abs(priceDiv), 1.) * 1.e-2;
               assertEquals(resDiv.get(Greek.FAIR_PRICE), priceDiv, refPriceDiv);
               final double deltaDiv =
                   BlackScholesFormulaRepository.delta(
                       SPOT, strike, TIME, vol, interest, interest - dividend, isCall);
               final double refDeltaDiv = Math.max(Math.abs(deltaDiv), 0.1) * 1.e-1;
               assertEquals(resDiv.get(Greek.DELTA), deltaDiv, refDeltaDiv);
               final double gammaDiv =
                   BlackScholesFormulaRepository.gamma(
                       SPOT, strike, TIME, vol, interest, interest - dividend);
               final double refGammaDiv = Math.max(Math.abs(gammaDiv), 0.1) * 1.e-1;
               assertEquals(resDiv.get(Greek.GAMMA), gammaDiv, refGammaDiv);
               final double thetaDiv =
                   BlackScholesFormulaRepository.theta(
                       SPOT, strike, TIME, vol, interest, interest - dividend, isCall);
               final double refThetaDiv = Math.max(Math.abs(thetaDiv), 0.1) * 1.e-1;
               assertEquals(resDiv.get(Greek.THETA), thetaDiv, refThetaDiv);
             }
           }
         }
       }
     }
   }
 }
  @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
  public void priceLatticeTrinomialTest() {
    final LatticeSpecification[] lattices =
        new LatticeSpecification[] {
          new CoxRossRubinsteinLatticeSpecification(),
          new JarrowRuddLatticeSpecification(),
          new TrigeorgisLatticeSpecification(),
          new TianLatticeSpecification()
        };

    final boolean[] tfSet = new boolean[] {true, false};
    for (final LatticeSpecification lattice : lattices) {
      for (final boolean isCall : tfSet) {
        for (final double strike : STRIKES) {
          for (final double interest : INTERESTS) {
            for (final double vol : VOLS) {
              final int nSteps = 621;
              for (final double dividend : DIVIDENDS) {
                final OptionFunctionProvider1D function =
                    new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
                final double exactDiv =
                    BlackScholesFormulaRepository.price(
                        SPOT, strike, TIME, vol, interest, interest - dividend, isCall);
                final double resDiv =
                    _modelTrinomial.getPrice(lattice, function, SPOT, vol, interest, dividend);
                final double refDiv =
                    lattice instanceof CoxRossRubinsteinLatticeSpecification
                        ? Math.max(exactDiv, 1.) * 1.e-2
                        : Math.max(exactDiv, 1.) * 1.e-3;
                assertEquals(resDiv, exactDiv, refDiv);
              }
            }
          }
        }
      }
    }
  }
  /** non-constant volatility and interest rate */
  @Test
  public void timeVaryingVolTest() {
    final LatticeSpecification lattice1 = new TimeVaryingLatticeSpecification();
    final double[] time_set = new double[] {0.5, 1.2};
    final int steps = 701;
    final double[] vol = new double[steps];
    final double[] rate = new double[steps];
    final double[] dividend = new double[steps];
    final int stepsTri = 661;
    final double[] volTri = new double[stepsTri];
    final double[] rateTri = new double[stepsTri];
    final double[] dividendTri = new double[stepsTri];
    final double constA = 0.01;
    final double constB = 0.001;
    final double constC = 0.1;
    final double constD = 0.05;

    final boolean[] tfSet = new boolean[] {true, false};
    for (final boolean isCall : tfSet) {
      for (final double strike : STRIKES) {
        for (final double time : time_set) {
          for (int i = 0; i < steps; ++i) {
            rate[i] = constA + constB * i * time / steps;
            vol[i] = constC + constD * Math.sin(i * time / steps);
            dividend[i] = 0.005;
          }
          for (int i = 0; i < stepsTri; ++i) {
            rateTri[i] = constA + constB * i * time / steps;
            volTri[i] = constC + constD * Math.sin(i * time / steps);
            dividendTri[i] = 0.005;
          }
          final double rateRef = constA + 0.5 * constB * time;
          final double volRef =
              Math.sqrt(
                  constC * constC
                      + 0.5 * constD * constD
                      + 2. * constC * constD / time * (1. - Math.cos(time))
                      - constD * constD * 0.25 / time * Math.sin(2. * time));

          final OptionFunctionProvider1D functionVanilla =
              new EuropeanVanillaOptionFunctionProvider(strike, time, steps, isCall);
          final double resPrice = _model.getPrice(functionVanilla, SPOT, vol, rate, dividend);
          final GreekResultCollection resGreeks =
              _model.getGreeks(functionVanilla, SPOT, vol, rate, dividend);

          final double resPriceConst =
              _model.getPrice(lattice1, functionVanilla, SPOT, volRef, rateRef, dividend[0]);
          final GreekResultCollection resGreeksConst =
              _model.getGreeks(lattice1, functionVanilla, SPOT, volRef, rateRef, dividend[0]);
          assertEquals(resPrice, resPriceConst, Math.max(Math.abs(resPriceConst), .1) * 1.e-1);
          assertEquals(
              resGreeks.get(Greek.FAIR_PRICE),
              resGreeksConst.get(Greek.FAIR_PRICE),
              Math.max(Math.abs(resGreeksConst.get(Greek.FAIR_PRICE)), 0.1) * 0.1);
          assertEquals(
              resGreeks.get(Greek.DELTA),
              resGreeksConst.get(Greek.DELTA),
              Math.max(Math.abs(resGreeksConst.get(Greek.DELTA)), 0.1) * 0.1);
          assertEquals(
              resGreeks.get(Greek.GAMMA),
              resGreeksConst.get(Greek.GAMMA),
              Math.max(Math.abs(resGreeksConst.get(Greek.GAMMA)), 0.1) * 0.1);
          assertEquals(
              resGreeks.get(Greek.THETA),
              resGreeksConst.get(Greek.THETA),
              Math.max(Math.abs(resGreeksConst.get(Greek.THETA)), 0.1));

          final OptionFunctionProvider1D functionTri =
              new EuropeanVanillaOptionFunctionProvider(strike, time, stepsTri, isCall);
          final double resPriceTrinomial =
              _modelTrinomial.getPrice(functionTri, SPOT, volTri, rateTri, dividendTri);
          assertEquals(
              resPriceTrinomial, resPriceConst, Math.max(Math.abs(resPriceConst), .1) * 1.e-1);
          final GreekResultCollection resGreeksTrinomial =
              _modelTrinomial.getGreeks(functionTri, SPOT, volTri, rateTri, dividendTri);
          assertEquals(
              resGreeksTrinomial.get(Greek.FAIR_PRICE),
              resGreeksConst.get(Greek.FAIR_PRICE),
              Math.max(Math.abs(resGreeksConst.get(Greek.FAIR_PRICE)), 0.1) * 0.1);
          assertEquals(
              resGreeksTrinomial.get(Greek.DELTA),
              resGreeksConst.get(Greek.DELTA),
              Math.max(Math.abs(resGreeksConst.get(Greek.DELTA)), 0.1) * 0.1);
          assertEquals(
              resGreeksTrinomial.get(Greek.GAMMA),
              resGreeksConst.get(Greek.GAMMA),
              Math.max(Math.abs(resGreeksConst.get(Greek.GAMMA)), 0.1) * 0.1);
          assertEquals(
              resGreeksTrinomial.get(Greek.THETA),
              resGreeksConst.get(Greek.THETA),
              Math.max(Math.abs(resGreeksConst.get(Greek.THETA)), 0.1));
        }
      }
    }
  }
  /** The dividend is cash or proportional to asset price */
  @Test
  public void greeksDiscreteDividendLatticeTest() {
    final LatticeSpecification[] lattices =
        new LatticeSpecification[] {
          new CoxRossRubinsteinLatticeSpecification(),
          new JarrowRuddLatticeSpecification(),
          new TrigeorgisLatticeSpecification(),
          new JabbourKraminYoungLatticeSpecification(),
          new TianLatticeSpecification(),
          new LeisenReimerLatticeSpecification()
        };

    final double[] propDividends = new double[] {0.01, 0.01, 0.01};
    final double[] cashDividends = new double[] {5., 10., 8.};
    final double[] dividendTimes = new double[] {TIME / 420., TIME / 203., TIME / 2.};

    final boolean[] tfSet = new boolean[] {true, false};
    for (final LatticeSpecification lattice : lattices) {
      for (final boolean isCall : tfSet) {
        for (final double strike : STRIKES) {
          for (final double interest : INTERESTS) {
            for (final double vol : VOLS) {
              final int nSteps = 401;
              final double resSpot =
                  SPOT
                      * (1. - propDividends[0])
                      * (1. - propDividends[1])
                      * (1. - propDividends[2]);
              final double modSpot =
                  SPOT
                      - cashDividends[0] * Math.exp(-interest * dividendTimes[0])
                      - cashDividends[1] * Math.exp(-interest * dividendTimes[1])
                      - cashDividends[2] * Math.exp(-interest * dividendTimes[2]);
              final double exactPriceProp =
                  BlackScholesFormulaRepository.price(
                      resSpot, strike, TIME, vol, interest, interest, isCall);
              final double exactDeltaProp =
                  BlackScholesFormulaRepository.delta(
                      resSpot, strike, TIME, vol, interest, interest, isCall);
              final double exactGammaProp =
                  BlackScholesFormulaRepository.gamma(
                      resSpot, strike, TIME, vol, interest, interest);
              final double exactThetaProp =
                  BlackScholesFormulaRepository.theta(
                      resSpot, strike, TIME, vol, interest, interest, isCall);

              final double appPriceCash =
                  BlackScholesFormulaRepository.price(
                      modSpot, strike, TIME, vol, interest, interest, isCall);
              final double appDeltaCash =
                  BlackScholesFormulaRepository.delta(
                      modSpot, strike, TIME, vol, interest, interest, isCall);
              final double appGammaCash =
                  BlackScholesFormulaRepository.gamma(
                      modSpot, strike, TIME, vol, interest, interest);
              final double appThetaCash =
                  BlackScholesFormulaRepository.theta(
                      modSpot, strike, TIME, vol, interest, interest, isCall);

              final OptionFunctionProvider1D function =
                  new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
              final DividendFunctionProvider cashDividend =
                  new CashDividendFunctionProvider(dividendTimes, cashDividends);
              final DividendFunctionProvider propDividend =
                  new ProportionalDividendFunctionProvider(dividendTimes, propDividends);
              final GreekResultCollection resProp =
                  _model.getGreeks(lattice, function, SPOT, vol, interest, propDividend);
              final GreekResultCollection resCash =
                  _model.getGreeks(lattice, function, SPOT, vol, interest, cashDividend);

              assertEquals(
                  resProp.get(Greek.FAIR_PRICE),
                  exactPriceProp,
                  Math.max(1., Math.abs(exactPriceProp)) * 1.e-2);
              assertEquals(
                  resProp.get(Greek.DELTA),
                  exactDeltaProp,
                  Math.max(1., Math.abs(exactDeltaProp)) * 1.e-1);
              assertEquals(
                  resProp.get(Greek.GAMMA),
                  exactGammaProp,
                  Math.max(1., Math.abs(exactGammaProp)) * 1.e-1);
              assertEquals(
                  resProp.get(Greek.THETA),
                  exactThetaProp,
                  Math.max(1., Math.abs(exactThetaProp)) * 1.e-1);

              assertEquals(
                  resCash.get(Greek.FAIR_PRICE),
                  appPriceCash,
                  Math.max(1., Math.abs(appPriceCash)) * 1.e-2);
              assertEquals(
                  resCash.get(Greek.DELTA),
                  appDeltaCash,
                  Math.max(1., Math.abs(appDeltaCash)) * 1.e-1);
              assertEquals(
                  resCash.get(Greek.GAMMA),
                  appGammaCash,
                  Math.max(1., Math.abs(appGammaCash)) * 1.e-1);
              assertEquals(
                  resCash.get(Greek.THETA), appThetaCash, Math.max(1., Math.abs(appThetaCash)));

              if (lattice instanceof CoxRossRubinsteinLatticeSpecification
                  || lattice instanceof JarrowRuddLatticeSpecification
                  || lattice instanceof TrigeorgisLatticeSpecification
                  || lattice instanceof TianLatticeSpecification) {
                final GreekResultCollection resPropTrinomial =
                    _modelTrinomial.getGreeks(lattice, function, SPOT, vol, interest, propDividend);
                final GreekResultCollection resCashTrinomial =
                    _modelTrinomial.getGreeks(lattice, function, SPOT, vol, interest, cashDividend);

                assertEquals(
                    resPropTrinomial.get(Greek.FAIR_PRICE),
                    resProp.get(Greek.FAIR_PRICE),
                    Math.max(1., Math.abs(resProp.get(Greek.FAIR_PRICE))) * 1.e-2);
                assertEquals(
                    resPropTrinomial.get(Greek.DELTA),
                    resProp.get(Greek.DELTA),
                    Math.max(1., Math.abs(resProp.get(Greek.DELTA))) * 1.e-2);

                assertEquals(
                    resPropTrinomial.get(Greek.GAMMA),
                    resProp.get(Greek.GAMMA),
                    Math.max(1., Math.abs(resProp.get(Greek.GAMMA))) * 1.e-2);
                assertEquals(
                    resPropTrinomial.get(Greek.THETA),
                    resProp.get(Greek.THETA),
                    Math.max(1., Math.abs(resProp.get(Greek.THETA))) * 1.e-1);

                assertEquals(
                    resCashTrinomial.get(Greek.FAIR_PRICE),
                    resCash.get(Greek.FAIR_PRICE),
                    Math.max(1., Math.abs(resCash.get(Greek.FAIR_PRICE))) * 1.e-2);
                assertEquals(
                    resCashTrinomial.get(Greek.DELTA),
                    resCash.get(Greek.DELTA),
                    Math.max(1., Math.abs(resCash.get(Greek.DELTA))) * 1.e-2);
                assertEquals(
                    resCashTrinomial.get(Greek.GAMMA),
                    resCash.get(Greek.GAMMA),
                    Math.max(1., Math.abs(resCash.get(Greek.GAMMA))) * 1.e-2);
                assertEquals(
                    resCashTrinomial.get(Greek.THETA),
                    resCash.get(Greek.THETA),
                    Math.max(1., Math.abs(resCash.get(Greek.THETA))) * 1.e-1);
              }
            }
          }
        }
      }
    }
  }
  /** The dividend is cash or proportional to asset price */
  @Test
  public void priceDiscreteDividendTest() {
    final LatticeSpecification[] lattices =
        new LatticeSpecification[] {
          new CoxRossRubinsteinLatticeSpecification(),
          new JarrowRuddLatticeSpecification(),
          new TrigeorgisLatticeSpecification(),
          new JabbourKraminYoungLatticeSpecification(),
          new TianLatticeSpecification(),
          new LeisenReimerLatticeSpecification()
        };

    final double[] propDividends = new double[] {0.01, 0.01, 0.01};
    final double[] cashDividends = new double[] {5., 10., 8.};
    final double[] dividendTimes = new double[] {TIME / 9., TIME / 3., TIME / 2.};

    final boolean[] tfSet = new boolean[] {true, false};
    for (final LatticeSpecification lattice : lattices) {
      for (final boolean isCall : tfSet) {
        for (final double strike : STRIKES) {
          for (final double interest : INTERESTS) {
            for (final double vol : VOLS) {
              final int[] choicesSteps = new int[] {33, 115};
              for (final int nSteps : choicesSteps) {
                final OptionFunctionProvider1D function =
                    new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
                final DividendFunctionProvider cashDividend =
                    new CashDividendFunctionProvider(dividendTimes, cashDividends);
                final DividendFunctionProvider propDividend =
                    new ProportionalDividendFunctionProvider(dividendTimes, propDividends);
                final double df = Math.exp(-interest * TIME);
                final double resSpot =
                    SPOT
                        * Math.exp(interest * TIME)
                        * (1. - propDividends[0])
                        * (1. - propDividends[1])
                        * (1. - propDividends[2]);
                final double modSpot =
                    SPOT
                        - cashDividends[0] * Math.exp(-interest * dividendTimes[0])
                        - cashDividends[1] * Math.exp(-interest * dividendTimes[1])
                        - cashDividends[2] * Math.exp(-interest * dividendTimes[2]);
                final double exactProp =
                    df * BlackFormulaRepository.price(resSpot, strike, TIME, vol, isCall);
                final double appCash =
                    BlackScholesFormulaRepository.price(
                        modSpot, strike, TIME, vol, interest, interest, isCall);
                final double resProp =
                    _model.getPrice(lattice, function, SPOT, vol, interest, propDividend);
                final double refProp = Math.max(exactProp, 1.) / Math.sqrt(nSteps);
                assertEquals(resProp, exactProp, refProp);
                final double resCash =
                    _model.getPrice(lattice, function, SPOT, vol, interest, cashDividend);
                final double refCash = Math.max(appCash, 1.) / Math.sqrt(nSteps);
                assertEquals(resCash, appCash, refCash);

                if (lattice instanceof CoxRossRubinsteinLatticeSpecification
                    || lattice instanceof JarrowRuddLatticeSpecification
                    || lattice instanceof TrigeorgisLatticeSpecification
                    || lattice instanceof TianLatticeSpecification) {
                  final double resPropTrinomial =
                      _modelTrinomial.getPrice(
                          lattice, function, SPOT, vol, interest, propDividend);
                  final double resCashTrinomial =
                      _modelTrinomial.getPrice(
                          lattice, function, SPOT, vol, interest, cashDividend);
                  assertEquals(
                      resPropTrinomial, resProp, Math.max(resProp, 1.) / Math.sqrt(nSteps));
                  assertEquals(
                      resCashTrinomial, resCash, Math.max(resCash, 1.) / Math.sqrt(nSteps));
                }
              }
            }
          }
        }
      }
    }
  }