@Test
  public void smoothConvergenceTest() {
    final LatticeSpecification lattice = new FlexibleLatticeSpecification();

    final boolean[] tfSet = new boolean[] {true, false};
    for (final boolean isCall : tfSet) {
      for (final double strike : STRIKES) {
        for (final double interest : INTERESTS) {
          for (final double vol : VOLS) {
            for (final double dividend : DIVIDENDS) {
              double prev = SPOT;
              double diff = 0.;
              if (interest - dividend > 0.) {
                for (int i = 0; i < 15; ++i) {
                  final int nSteps = 10 + 50 * i;
                  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 =
                      _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
                  diff = Math.abs(exactDiv - resDiv);
                  assertTrue(diff < prev);
                  prev = diff;
                }
              }
            }
          }
        }
      }
    }
  }
 @Test
 public void greekTest() {
   final LatticeSpecification[] lattices =
       new LatticeSpecification[] {
         new CoxRossRubinsteinLatticeSpecification(),
         new JarrowRuddLatticeSpecification(),
         new TrigeorgisLatticeSpecification(),
         new JabbourKraminYoungLatticeSpecification(),
         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[] choicesSteps = new int[] {31, 112, 301};
             for (final int nSteps : choicesSteps) {
               for (final double dividend : DIVIDENDS) {
                 final OptionFunctionProvider1D function =
                     new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
                 final GreekResultCollection resDiv =
                     _model.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.) / Math.sqrt(nSteps);
                 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), 1.) / Math.sqrt(nSteps);
                 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), 1.) / Math.sqrt(nSteps);
                 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), 1.) / Math.sqrt(nSteps);
                 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 greekLeisenReimerTest() {
    final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();

    final boolean[] tfSet = new boolean[] {true, false};
    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[] {31, 109, 301};
            for (final int nSteps : choicesSteps) {
              for (final double dividend : DIVIDENDS) {
                final OptionFunctionProvider1D function =
                    new EuropeanVanillaOptionFunctionProvider(strike, TIME, nSteps, isCall);
                final GreekResultCollection resDiv =
                    _model.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.) / nSteps;
                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), 1.) / nSteps;
                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), 1.) / nSteps;
                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), 1.) / nSteps;
                assertEquals(resDiv.get(Greek.THETA), thetaDiv, refThetaDiv * 10.);
              }
            }
          }
        }
      }
    }
  }
  @Test
  public void priceLatticeTest() {
    final LatticeSpecification[] lattices =
        new LatticeSpecification[] {
          new CoxRossRubinsteinLatticeSpecification(),
          new JarrowRuddLatticeSpecification(),
          new TrigeorgisLatticeSpecification(),
          new JabbourKraminYoungLatticeSpecification(),
          new TianLatticeSpecification(),
          new LeisenReimerLatticeSpecification()
        };

    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[] {31, 115, 301};
              for (final int nSteps : choicesSteps) {
                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 =
                      _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
                  final double refDiv =
                      (lattice instanceof LeisenReimerLatticeSpecification)
                          ? Math.max(exactDiv, 1.) / nSteps / nSteps
                          : Math.max(exactDiv, 1.) / Math.sqrt(nSteps);
                  assertEquals(resDiv, exactDiv, refDiv);
                }
              }
            }
          }
        }
      }
    }
  }
  @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);
              }
            }
          }
        }
      }
    }
  }
  /** 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));
                }
              }
            }
          }
        }
      }
    }
  }