Package org.jquantlib.testsuite.instruments

Source Code of org.jquantlib.testsuite.instruments.EuropeanOptionTest

/*
Copyright (C) 2007 Richard Gomes

This source code is release under the BSD License.

This file is part of JQuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://jquantlib.org/

JQuantLib is free software: you can redistribute it and/or modify it
under the terms of the JQuantLib license.  You should have received a
copy of the license along with this program; if not, please email
<jquant-devel@lists.sourceforge.net>. The license is also available online at
<http://www.jquantlib.org/index.php/LICENSE.TXT>.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.

JQuantLib is based on QuantLib. http://quantlib.org/
When applicable, the original copyright notice follows this notice.
*/

/*
Copyright (C) 2003, 2007 Ferdinando Ametrano
Copyright (C) 2003, 2007 StatPro Italia srl

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/

QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license.  You should have received a
copy of the license along with this program; if not, please email
<quantlib-dev@lists.sf.net>. The license is also available online at
<http://quantlib.org/license.shtml>.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

package org.jquantlib.testsuite.instruments;

import static org.junit.Assert.fail;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.jquantlib.QL;
import org.jquantlib.Settings;
import org.jquantlib.daycounters.Actual360;
import org.jquantlib.daycounters.DayCounter;
import org.jquantlib.exercise.EuropeanExercise;
import org.jquantlib.exercise.Exercise;
import org.jquantlib.instruments.AssetOrNothingPayoff;
import org.jquantlib.instruments.CashOrNothingPayoff;
import org.jquantlib.instruments.EuropeanOption;
import org.jquantlib.instruments.GapPayoff;
import org.jquantlib.instruments.Option;
import org.jquantlib.instruments.PlainVanillaPayoff;
import org.jquantlib.instruments.StrikedTypePayoff;
import org.jquantlib.instruments.VanillaOption;
import org.jquantlib.instruments.Option.Type;
import org.jquantlib.lang.annotation.NonNegative;
import org.jquantlib.methods.lattices.AdditiveEQPBinomialTree;
import org.jquantlib.methods.lattices.CoxRossRubinstein;
import org.jquantlib.methods.lattices.JarrowRudd;
import org.jquantlib.methods.lattices.Joshi4;
import org.jquantlib.methods.lattices.LeisenReimer;
import org.jquantlib.methods.lattices.Tian;
import org.jquantlib.methods.lattices.Trigeorgis;
import org.jquantlib.pricingengines.AnalyticEuropeanEngine;
import org.jquantlib.pricingengines.PricingEngine;
import org.jquantlib.pricingengines.vanilla.BinomialVanillaEngine;
import org.jquantlib.pricingengines.vanilla.IntegralEngine;
import org.jquantlib.pricingengines.vanilla.finitedifferences.FDEuropeanEngine;
import org.jquantlib.processes.BlackScholesMertonProcess;
import org.jquantlib.processes.GeneralizedBlackScholesProcess;
import org.jquantlib.quotes.Handle;
import org.jquantlib.quotes.Quote;
import org.jquantlib.quotes.SimpleQuote;
import org.jquantlib.termstructures.BlackVolTermStructure;
import org.jquantlib.termstructures.YieldTermStructure;
import org.jquantlib.testsuite.util.Flag;
import org.jquantlib.testsuite.util.Utilities;
import org.jquantlib.time.Date;
import org.jquantlib.time.Period;
import org.junit.Test;


/**
* European Options test suite
*
* @author Richard Gomes
*/
public class EuropeanOptionTest {

    // private final Date today;

    public EuropeanOptionTest() {
        QL.info("::::: "+this.getClass().getSimpleName()+" :::::");
    }


    private static class EuropeanOptionData {
        private final Option.Type type;            // option type
        private final /*@Real*/ double strike;    // option strike price
        private final double s;                    // spot // FIXME: any specific @annotation?
        private final /*@Real*/ double  q;        // dividend
        private final /*@Rate*/ double  r;         // risk-free rate
        private final /*@Time*/ double  t;         // time to maturity
        private final /*@Volatility*/ double v;    // volatility
        private final /*@Real*/ double result;    // expected result
        private final double tol;                  // tolerance // FIXME: any specific @annotation?

        public EuropeanOptionData(
                final Option.Type type,
                /*@Real*/ final double strike,
                final double s, /*@Real*/ final double  q,
                /*@Rate*/ final double  r,
                /*@Time*/ final double  t,
                /*@Volatility*/ final double v,
                /*@Real*/ final double result,
                final double tol) {
            this.type = type;
            this.strike = strike;
            this.s = s;
            this.q = q;
            this.r = r;
            this.t = t;
            this.v = v;
            this.result = result;
            this.tol = tol;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append('[');
            sb.append(type).append(", ");
            sb.append(strike).append(", ");
            sb.append(s).append(", ");
            sb.append(q).append(", ");
            sb.append(r).append(", ");
            sb.append(t).append(", ");
            sb.append(v).append(", ");
            sb.append(result).append(", ");
            sb.append(tol);
            sb.append(']');
            return sb.toString();
        }
    }

    private enum EngineType {
        Analytic,
        JR, CRR, EQP, TGEO, TIAN, LR, JOSHI,
        FiniteDifferences,
        Integral,
        PseudoMonteCarlo, QuasiMonteCarlo; }


    private GeneralizedBlackScholesProcess makeProcess(
            final Quote u,
            final YieldTermStructure q,
            final YieldTermStructure r,
            final BlackVolTermStructure vol) {
        return new BlackScholesMertonProcess(
                new Handle<Quote>(u),
                new Handle<YieldTermStructure>(q),
                new Handle<YieldTermStructure>(r),
                new Handle<BlackVolTermStructure>(vol));
    }


    private VanillaOption makeOption(
            final StrikedTypePayoff payoff,
            final Exercise exercise,
            final SimpleQuote u,
            final YieldTermStructure q,
            final YieldTermStructure r,
            final BlackVolTermStructure vol,
            final EngineType engineType,
            final int binomialSteps,
            final int samples) {

        final GeneralizedBlackScholesProcess stochProcess = makeProcess(u,q,r,vol);
        final PricingEngine engine;

        switch (engineType) {
            case Analytic:
                engine = new AnalyticEuropeanEngine(stochProcess);
                break;
            case JR:
                engine = new BinomialVanillaEngine<JarrowRudd>(stochProcess, binomialSteps) {};
                break;
            case CRR:
                engine = new BinomialVanillaEngine<CoxRossRubinstein>(stochProcess, binomialSteps) {};
                break;
            case EQP:
                engine = new BinomialVanillaEngine<AdditiveEQPBinomialTree>(stochProcess, binomialSteps) {};
                break;
            case TGEO:
                engine = new BinomialVanillaEngine<Trigeorgis>(stochProcess, binomialSteps) {};
                break;
            case TIAN:
                engine = new BinomialVanillaEngine<Tian>(stochProcess, binomialSteps) {};
                break;
            case LR:
                engine = new BinomialVanillaEngine<LeisenReimer>(stochProcess, binomialSteps) {};
                break;
            case JOSHI:
                engine = new BinomialVanillaEngine<Joshi4>(stochProcess, binomialSteps) {};
                break;
            case FiniteDifferences:
                engine = new FDEuropeanEngine(stochProcess, binomialSteps,samples);
                break;
            case Integral:
                engine = new IntegralEngine(stochProcess);
                break;

                //        case PseudoMonteCarlo:
                //          engine = MakeMCEuropeanEngine<PseudoRandom>().withSteps(1)
                //                                                       .withSamples(samples)
                //                                                       .withSeed(42);
                //          break;

                //        case QuasiMonteCarlo:
                //          engine = MakeMCEuropeanEngine<LowDiscrepancy>().withSteps(1)
                //                                                         .withSamples(samples);
                //          break;

            default:
                throw new UnsupportedOperationException("unknown engine type: "+engineType);
        }

        final VanillaOption option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);
        return option;
    }




    //  std::string engineTypeToString(EngineType type) {
    //      switch (type) {
    //        case Analytic:
    //          return "analytic";
    //        case JR:
    //          return "Jarrow-Rudd";
    //        case CRR:
    //          return "Cox-Ross-Rubinstein";
    //        case EQP:
    //          return "EQP";
    //        case TGEO:
    //          return "Trigeorgis";
    //        case TIAN:
    //          return "Tian";
    //        case LR:
    //          return "LeisenReimer";
    //        case JOSHI:
    //          return "Joshi";
    //        case FiniteDifferences:
    //          return "FiniteDifferences";
    //      case Integral:
    //          return "Integral";
    //        case PseudoMonteCarlo:
    //          return "MonteCarlo";
    //        case QuasiMonteCarlo:
    //          return "Quasi-MonteCarlo";
    //        default:
    //          QL_FAIL("unknown engine type");
    //      }
    //  }

    private int timeToDays(/*@Time*/ final double t) {
        return (int) (t*360+0.5);
    }


    @Test
    public void testValues() {

        QL.info("Testing European option values...");

        /**
         *  The data below are from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
         */
        final EuropeanOptionData values[] = new EuropeanOptionData[] {
                // pag 2-8
                //                              type,     strike,   spot,    q,    r,    t,  vol,   value,    tol
                new EuropeanOptionData( Option.Type.Call,  65.0060.00, 0.00, 0.08, 0.25, 0.302.1334, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,   95.00, 100.00, 0.05, 0.10, 0.50, 0.202.4648, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,   19.0019.00, 0.10, 0.10, 0.75, 0.281.7011, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call,  19.0019.00, 0.10, 0.10, 0.75, 0.281.7011, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call,   1.60,   1.56, 0.08, 0.06, 0.50, 0.120.0291, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,   70.0075.00, 0.05, 0.10, 0.50, 0.354.0870, 1.0e-4),
                // pag 24
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.10, 0.150.0205, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.151.8734, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.159.9413, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.10, 0.250.3150, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.253.1217, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 10.3556, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.10, 0.350.9474, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.354.3693, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 11.1381, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.50, 0.150.8069, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.154.0232, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 10.5769, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.50, 0.252.7026, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.256.6997, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 12.7857, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.0090.00, 0.10, 0.10, 0.50, 0.354.9329, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.359.3679, 1.0e-4),
                new EuropeanOptionData( Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 15.3086, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.10, 0.159.9210, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.151.8734, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.150.0408, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.10, 0.25, 10.2155, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.253.1217, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.250.4551, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.10, 0.35, 10.8479, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.354.3693, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.351.2376, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.50, 0.15, 10.3192, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.154.0232, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.151.0646, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.50, 0.25, 12.2149, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.256.6997, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.253.2734, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.0090.00, 0.10, 0.10, 0.50, 0.35, 14.4452, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.359.3679, 1.0e-4),
                new EuropeanOptionData( Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.355.7963, 1.0e-4),
                // pag 27
                new EuropeanOptionData( Option.Type.Call,  40.0042.00, 0.08, 0.04, 0.75, 0.355.0975, 1.0e-4)
        };

        final Date today = new Settings().evaluationDate();

        final DayCounter dc = new Actual360();

        final SimpleQuote           spot  = new SimpleQuote(0.0);
        final SimpleQuote           qRate = new SimpleQuote(0.0);
        final YieldTermStructure    qTS   = Utilities.flatRate(today, qRate, dc);
        final SimpleQuote           rRate = new SimpleQuote(0.0);
        final YieldTermStructure    rTS   = Utilities.flatRate(today, rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.0);
        final BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc);

        for (int i=0; i<values.length-1; i++) {

            QL.debug(values[i].toString());

            final StrikedTypePayoff payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
            final Date exDate = today.add( timeToDays(values[i].t) );
            final Exercise exercise = new EuropeanExercise(exDate);

            spot.setValue(values[i].s);
            qRate.setValue(values[i].q);
            rRate.setValue(values[i].r);
            vol.setValue(values[i].v);


            final BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(
                    new Handle<Quote>(spot),
                    new Handle<YieldTermStructure>(qTS),
                    new Handle<YieldTermStructure>(rTS),
                    new Handle<BlackVolTermStructure>(volTS));

            final PricingEngine engine = new AnalyticEuropeanEngine(stochProcess);

            final EuropeanOption option = new EuropeanOption(payoff, exercise);
            option.setPricingEngine(engine);

            final double calculated = option.NPV();
            final double error = Math.abs(calculated-values[i].result);
            final double tolerance = values[i].tol;

            final StringBuilder sb = new StringBuilder();
            sb.append("error ").append(error).append(" .gt. tolerance ").append(tolerance).append('\n');
            sb.append("    calculated ").append(calculated).append('\n');
            sb.append("    type ").append(values[i].type).append('\n');
            sb.append("    strike ").append(values[i].strike).append('\n');
            sb.append("    s ").append(values[i].s).append('\n');
            sb.append("    q ").append(values[i].q).append('\n');
            sb.append("    r ").append(values[i].r).append('\n');
            sb.append("    t ").append(values[i].t).append('\n');
            sb.append("    v ").append(values[i].v).append('\n');
            sb.append("    result ").append(values[i].result).append('\n');
            sb.append("    tol ").append(values[i].tol); // .append('\n');

            if (error<=tolerance) {
                QL.info(" error="+error);
            } else {
                fail(exercise + " " + payoff.optionType() + " option with " + payoff + " payoff:\n"
                        + "    spot value:       " + values[i].s + "\n"
                        + "    strike:           " + payoff.strike() + "\n"
                        + "    dividend yield:   " + values[i].q + "\n"
                        + "    risk-free rate:   " + values[i].r + "\n"
                        + "    reference date:   " + today + "\n"
                        + "    maturity:         " + values[i].t + "\n"
                        + "    volatility:       " + values[i].v + "\n\n"
                        + "    expected:         " + values[i].result + "\n"
                        + "    calculated:       " + calculated + "\n"
                        + "    error:            " + error + "\n"
                        + "    tolerance:        " + tolerance);
            }
        }
    }

    @Test
    public void testGreekValues(){
        QL.info("Testing European option greek values...");

        //
        // The data below are from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 pag 11-16
        //

        final EuropeanOptionData values[] = {
                //                     type,             strike, spot,   q,    r,    t,        vol,  value,    tolerance
                //                     ================  ======  ======  ====  ====  ========  ====  ========  =========
                new EuropeanOptionData(Option.Type.Call, 100.00, 105.00, 0.10, 0.10, 0.500000, 0.36,   0.5946, 0),
                new EuropeanOptionData(Option.Type.Put,  100.00, 105.00, 0.10, 0.10, 0.500000, 0.36,  -0.3566, 0),
                new EuropeanOptionData(Option.Type.Put,  100.00, 105.00, 0.10, 0.10, 0.500000, 0.36,  -4.8775, 0),
                new EuropeanOptionData(Option.Type.Call,  60.0055.00, 0.00, 0.10, 0.750000, 0.30,   0.0278, 0),
                new EuropeanOptionData(Option.Type.Put,   60.0055.00, 0.00, 0.10, 0.750000, 0.30,   0.0278, 0),
                new EuropeanOptionData(Option.Type.Call,  60.0055.00, 0.00, 0.10, 0.750000, 0.3018.9358, 0),
                new EuropeanOptionData(Option.Type.Put,   60.0055.00, 0.00, 0.10, 0.750000, 0.3018.9358, 0),
                new EuropeanOptionData(Option.Type.Put,  405.00, 430.00, 0.05, 0.07, 1.0/12.0, 0.20, -31.1924, 0),
                new EuropeanOptionData(Option.Type.Put,  405.00, 430.00, 0.05, 0.07, 1.0/12.0, 0.20,  -0.0855, 0),
                new EuropeanOptionData(Option.Type.Call,  75.0072.00, 0.00, 0.09, 1.000000, 0.1938.7325, 0),
                new EuropeanOptionData(Option.Type.Put,  490.00, 500.00, 0.05, 0.08, 0.250000, 0.1542.2254, 0)
        };

        // tolerance is fixed
        final double tolerance = 1e-4;


        final Date today = new Settings().evaluationDate();

        final DayCounter         dc    = new Actual360();
        final SimpleQuote        spot  = new SimpleQuote(0.0);
        final SimpleQuote        qRate = new SimpleQuote(0.0);
        final YieldTermStructure qTS   = Utilities.flatRate(today, qRate, dc);

        final SimpleQuote           rRate = new SimpleQuote(0.0);
        final YieldTermStructure    rTS   = Utilities.flatRate(today, rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.0);
        final BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc);


        StrikedTypePayoff payoff;
        Date exDate;
        Exercise exercise;
        double calculated;
        double error;

        int i = -1;

        // testing delta 1
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        final BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(
                new Handle<Quote>(spot),
                new Handle<YieldTermStructure>(qTS),
                new Handle<YieldTermStructure>(rTS),
                new Handle<BlackVolTermStructure>(volTS));
        final PricingEngine engine = new AnalyticEuropeanEngine(stochProcess);

        VanillaOption option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.delta();
        error = Math.abs(calculated - values[i].result);

        if (error > tolerance) {
            REPORT_FAILURE("delta", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing delta 2
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.delta();
        error = Math.abs(calculated - values[i].result);
        if(error>tolerance) {
            REPORT_FAILURE("delta", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing elasticity
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.elasticity();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("elasticity", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        // testing gamma 1
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.gamma();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("gamma", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        // testing gamma 2
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.gamma();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("gamma", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing vega 1
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.vega();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("vega", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing vega 2
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.vega();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("vega", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing theta
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.theta();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("theta", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }


        //testing theta per day
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.thetaPerDay();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("theta per day", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }


        //testing rho
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.rho();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("rho", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }

        //testing dividend rho
        i++;
        payoff = new PlainVanillaPayoff(values[i].type, values[i].strike);
        exDate = today.add(timeToDays(values[i].t));
        exercise = new EuropeanExercise(exDate);
        spot.setValue(values[i].s);
        qRate.setValue(values[i].q);
        rRate.setValue(values[i].r);
        vol.setValue(values[i].v);

        option = new EuropeanOption(payoff, exercise);
        option.setPricingEngine(engine);

        calculated = option.dividendRho();
        error = Math.abs(Math.abs(calculated - values[i].result));
        if(error>tolerance) {
            REPORT_FAILURE("dividend rho", payoff, exercise, values[i].s, values[i].q, values[i].r, today, values[i].v,
                    values[i].result, calculated, error, tolerance);
        }
    }


    @Test
    public void testGreeks() {
        QL.info("Testing analytic European option greeks...");

        final Map<String,Double> tolerance = new HashMap<String, Double>();
        tolerance.put("delta"1.0e-5);
        tolerance.put("gamma"1.0e-5);
        tolerance.put("theta"1.0e-5);
        tolerance.put("rho",    1.0e-5);
        tolerance.put("divRho", 1.0e-5);
        tolerance.put("vega",   1.0e-5);

        final Map<String,Double> expected = new HashMap<String, Double>();
        final Map<String,Double> calculated = new HashMap<String, Double>();

        final Option.Type types[] = { Option.Type.Call, Option.Type.Put };
        final double strikes[] = { 50.0, 99.5, 100.0, 100.5, 150.0 };
        final double underlyings[] = { 100.0 };
        final double qRates[] = { 0.04, 0.05, 0.06 };
        final double rRates[] = { 0.01, 0.05, 0.15 };
        final double residualTimes[] = { 1.0, 2.0 };
        final double vols[] = { 0.11, 0.50, 1.20 };

        final DayCounter dc = new Actual360();
        new Settings().setEvaluationDate(Date.todaysDate());
        final Date today = new Settings().evaluationDate();

        final SimpleQuote           spot  = new SimpleQuote(0.0);
        final SimpleQuote           qRate = new SimpleQuote(0.0);
        final YieldTermStructure    qTS   = Utilities.flatRate(qRate, dc);
        final SimpleQuote           rRate = new SimpleQuote(0.0);
        final YieldTermStructure    rTS   = Utilities.flatRate(rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.0);
        final BlackVolTermStructure volTS = Utilities.flatVol(vol, dc);

        for (final Type type : types) {
            for (final double strike : strikes) {
                for (final double residualTime : residualTimes) {

                    final Date exDate = today.add( timeToDays(residualTime) ); //TODO: code review
                    final Exercise exercise = new EuropeanExercise(exDate);

                    for (int kk=0; kk<4; kk++) {
                        StrikedTypePayoff payoff = null;
                        // option to check
                        if (kk==0) {
                            payoff = new PlainVanillaPayoff(type, strike);
                        } else if (kk==1) {
                            //FIXME check constructor
                            payoff = new CashOrNothingPayoff(type, strike, 100);
                        } else if (kk==2) {
                            payoff = new AssetOrNothingPayoff(type, strike);
                        } else if (kk==3) {
                            payoff = new GapPayoff(type, strike, 100);
                        }

                        final BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(
                                new Handle<Quote>(spot),
                                new Handle<YieldTermStructure>(qTS),
                                new Handle<YieldTermStructure>(rTS),
                                new Handle<BlackVolTermStructure>(volTS));
                        final PricingEngine engine = new AnalyticEuropeanEngine(stochProcess);

                        if (payoff==null)
                            throw new IllegalArgumentException();

                        final EuropeanOption option = new EuropeanOption(payoff, exercise);
                        option.setPricingEngine(engine);

                        for (final double u : underlyings) {
                            for (final double q : qRates) {
                                for (final double r : rRates) {
                                    for (final double v : vols) {
                                        //something wrong here for vanilla payoff?
                                        spot.setValue(u);
                                        qRate.setValue(q);
                                        rRate.setValue(r);
                                        vol.setValue(v);

                                        final double value = option.NPV();
                                        final double delta = option.delta();
                                        final double gamma = option.gamma();
                                        final double theta = option.theta();
                                        final double rho   = option.rho();
                                        final double drho  = option.dividendRho();
                                        final double vega  = option.vega();

                                        calculated.put("delta",  delta);
                                        calculated.put("gamma",  gamma);
                                        calculated.put("theta",  theta);
                                        calculated.put("rho",    rho);
                                        calculated.put("divRho", drho);
                                        calculated.put("vega",   vega);

                                        if (value > spot.value()*1.0e-5) {
                                            // perturb spot and get delta and gamma
                                            final double du = u*1.0e-4;
                                            spot.setValue(u+du);
                                            double value_p = option.NPV();
                                            final double delta_p = option.delta();
                                            spot.setValue(u-du);

                                            double value_m = option.NPV();
                                            final double delta_m = option.delta();
                                            spot.setValue(u);
                                            expected.put("delta", (value_p - value_m)/(2*du));
                                            expected.put("gamma", (delta_p - delta_m)/(2*du));

                                            // perturb rates and get rho and dividend rho
                                            final double dr = r*1.0e-4;
                                            rRate.setValue(r+dr);
                                            value_p = option.NPV();
                                            rRate.setValue(r-dr);
                                            value_m = option.NPV();
                                            rRate.setValue(r);
                                            expected.put("rho", (value_p - value_m)/(2*dr));

                                            final double dq = q*1.0e-4;
                                            qRate.setValue(q+dq);
                                            value_p = option.NPV();
                                            qRate.setValue(q-dq);
                                            value_m = option.NPV();
                                            qRate.setValue(q);
                                            expected.put("divRho",(value_p - value_m)/(2*dq));

                                            // perturb volatility and get vega
                                            final double dv = v*1.0e-4;
                                            vol.setValue(v+dv);
                                            value_p = option.NPV();
                                            vol.setValue(v-dv);
                                            value_m = option.NPV();
                                            vol.setValue(v);
                                            expected.put("vega",(value_p - value_m)/(2*dv));

                                            // perturb date and get theta
                                            final Date yesterday = today.sub(1);
                                            final Date tomorrow  = today.add(1);
                                            final double dT = dc.yearFraction(yesterday, tomorrow);
                                            new Settings().setEvaluationDate(yesterday);
                                            value_m = option.NPV();
                                            new Settings().setEvaluationDate(tomorrow);
                                            value_p = option.NPV();
                                            new Settings().setEvaluationDate(Date.todaysDate());
                                            expected.put("theta", (value_p - value_m)/dT);

                                            // compare
                                            for (final Entry<String, Double> it: calculated.entrySet()){

                                                final String greek = it.getKey();
                                                final double expct = expected.get(greek);
                                                final double calcl = calculated.get(greek);
                                                final double tol   = tolerance.get(greek);

                                                final double error = Utilities.relativeError(expct,calcl,u);
                                                if (error>tol) {
                                                    REPORT_FAILURE(greek, payoff, exercise, u, q, r, today, v, expct, calcl, error, tol);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

    }


    @Test
    public void testImpliedVol() {

        QL.info("Testing European option implied volatility...");

        final int maxEvaluations = 100;
        final double tolerance = 1.0e-6;

        // test options
        final Option.Type types[] = { Option.Type.Call, Option.Type.Put };
        final double strikes[] = { 90.0, 99.5, 100.0, 100.5, 110.0 };
        final int lengths[] = { 36, 180, 360, 1080 };

        // test data
        final double underlyings[] = { 90.0, 95.0, 99.9, 100.0, 100.1, 105.0, 110.0 };
        final double qRates[] = { 0.01, 0.05, 0.10 };
        final double rRates[] = { 0.01, 0.05, 0.10 };
        final double vols[] = { 0.01, 0.20, 0.30, 0.70, 0.90 };

        final DayCounter dc = new Actual360();
        final Date today = Date.todaysDate();

        final SimpleQuote           spot  = new SimpleQuote(0.0);
        final SimpleQuote           qRate = new SimpleQuote(0.0);
        final YieldTermStructure    qTS   = Utilities.flatRate(today, qRate, dc);
        final SimpleQuote           rRate = new SimpleQuote(0.0);
        final YieldTermStructure    rTS   = Utilities.flatRate(today, rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.0);
        final BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc);

        for (final Type type : types) {
            for (final double strike2 : strikes) {
                for (final int length : lengths) {
                    // option to check
                    final Date exDate = today.add( length );
                    final Exercise exercise = new EuropeanExercise(exDate);
                    final StrikedTypePayoff payoff = new PlainVanillaPayoff(type, strike2);
                    final VanillaOption option = makeOption(payoff, exercise, spot, qTS, rTS, volTS, EngineType.Analytic, 0, 0);

                    final GeneralizedBlackScholesProcess process = makeProcess(spot, qTS, rTS,volTS);

                    for (final double u : underlyings) {
                        for (final double q : qRates) {
                            for (final double r : rRates) {
                                for (final double v : vols) {
                                    spot.setValue(u);
                                    qRate.setValue(q);
                                    rRate.setValue(r);
                                    vol.setValue(v);

                                    final double value = option.NPV();
                                    double implVol = 0.0; // just to remove a warning...
                                    if (value != 0.0) {
                                        // shift guess somehow
                                        vol.setValue(v*0.5);
                                        if (Math.abs(value-option.NPV()) <= 1.0e-12) {
                                            // flat price vs vol --- pointless (and
                                            // numerically unstable) to solve
                                            continue;
                                        }

                                        implVol = option.impliedVolatility(value, process, tolerance, maxEvaluations);

                                        if (Math.abs(implVol-v) > tolerance) {
                                            // the difference might not matter
                                            vol.setValue(implVol);
                                            final double value2 = option.NPV();
                                            final double error = Utilities.relativeError(value,value2,u);
                                            if (error > tolerance) {
                                                fail(
                                                        type + " option :\n"
                                                        + "    spot value:          " + u + "\n"
                                                        + "    strike:              "
                                                        + strike2 + "\n"
                                                        + "    dividend yield:      "
                                                        + (q) + "\n"
                                                        + "    risk-free rate:      "
                                                        + (r) + "\n"
                                                        + "    maturity:            "
                                                        + exDate + "\n\n"
                                                        + "    original volatility: "
                                                        + (v) + "\n"
                                                        + "    price:               "
                                                        + value + "\n"
                                                        + "    implied volatility:  "
                                                        + (implVol)
                                                        + "\n"
                                                        + "    corresponding price: "
                                                        + value2 + "\n"
                                                        + "    error:               " + error);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @Test
    public void testImpliedVolContainment(){
        QL.info("Testing self-containment of implied volatility calculation... running");

        final int maxEvaluations = 100;
        final double tolerance = 1.0e-6;

        final Date today = new Settings().evaluationDate();

        final DayCounter dc = new Actual360();
        final SimpleQuote           spot  = new SimpleQuote(100.0);
        final Quote                 u     = spot;
        final SimpleQuote           qRate = new SimpleQuote(0.05);
        final YieldTermStructure    qTS   = Utilities.flatRate(today, qRate, dc);
        final SimpleQuote           rRate = new SimpleQuote(0.003);
        final YieldTermStructure    rTS   = Utilities.flatRate(today, rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.20);
        final BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc);

        final Date exerciseDate = today.add(Period.ONE_YEAR_FORWARD);
        final Exercise exercise = new EuropeanExercise(exerciseDate);
        final StrikedTypePayoff payoff = new PlainVanillaPayoff(Option.Type.Call, 100);

        final BlackScholesMertonProcess stochProcess = new BlackScholesMertonProcess(
                new Handle<Quote>(u),
                new Handle<YieldTermStructure>(qTS),
                new Handle<YieldTermStructure>(rTS),
                new Handle<BlackVolTermStructure>(volTS));
        final PricingEngine engine = new AnalyticEuropeanEngine(stochProcess);

        final EuropeanOption option1 = new EuropeanOption(payoff, exercise);
        final EuropeanOption option2 = new EuropeanOption(payoff, exercise);
        option1.setPricingEngine(engine);
        option2.setPricingEngine(engine);

        // test
        final double refValue = option2.NPV();

        final Flag f = new Flag();
        option2.addObserver(f);

        option1.impliedVolatility(refValue*1.5, stochProcess, tolerance, maxEvaluations);

        if (f.isUp()) {
            fail("implied volatility calculation triggered a change in another instrument");
        }

        option2.recalculate();
        if (Math.abs(option2.NPV() - refValue) >= 1.0e-8) {
            fail("implied volatility calculation changed the value "
                    + "of another instrument: \n"
                    + "previous value: " + refValue + "\n"
                    + "current value:  " + option2.NPV());
        }

        vol.setValue(vol.value()*1.5);

        if (!f.isUp()) {
            fail("volatility change not notified");
        }

        if (Math.abs(option2.NPV() - refValue) <= 1.0e-8) {
            fail("volatility change did not cause the value to change");
        }
    }



    private void testEngineConsistency(
            final EngineType engine,
            final int binomialSteps, final int samples,
            final Map<String, Double> tolerance) {

        testEngineConsistency(engine, binomialSteps, samples, tolerance, false);
    }

    private void testEngineConsistency(
            final EngineType engine,
            final int binomialSteps, final int samples,
            final Map<String, Double> tolerance, final boolean testGreeks) {

        // QL_TEST_START_TIMING

        final Map<String, Double> calculated = new HashMap<String, Double>();
        final Map<String, Double> expected = new HashMap<String, Double>();

        // test options
        final Option.Type types[] = { Option.Type.Call, Option.Type.Put };
        final double strikes[] = { 75.0, 100.0, 125.0 };
        final int lengths[] = { 1 };

        // test data
        final double underlyings[] = { 100.0 };
        final double /* @Rate */qRates[] = { 0.00, 0.05 };
        final double /* @Rate */rRates[] = { 0.01, 0.05, 0.15 };
        final double /* @Volatility */vols[] = { 0.11, 0.50, 1.20 };

        final Date today = new Settings().evaluationDate();

        final DayCounter dc = new Actual360();

        final SimpleQuote           spot  = new SimpleQuote(0.0);
        final SimpleQuote           qRate = new SimpleQuote(0.0);
        final YieldTermStructure    qTS   = Utilities.flatRate(today, qRate, dc);
        final SimpleQuote           rRate = new SimpleQuote(0.0);
        final YieldTermStructure    rTS   = Utilities.flatRate(today, rRate, dc);
        final SimpleQuote           vol   = new SimpleQuote(0.0);
        final BlackVolTermStructure volTS = Utilities.flatVol(today, vol, dc);

        for (final Type type : types) {
            for (final double strike3 : strikes) {
                for (final int length2 : lengths) {

                    final Date exDate = today.add(timeToDays(length2));
                    final Exercise exercise = new EuropeanExercise(exDate);

                    final StrikedTypePayoff payoff = new PlainVanillaPayoff(type, strike3);

                    // reference option
                    final VanillaOption refOption = makeOption(payoff, exercise, spot, qTS, rTS, volTS, EngineType.Analytic, 0, 0);
                    // option to check
                    final VanillaOption option = makeOption(payoff, exercise, spot, qTS, rTS, volTS, engine, binomialSteps, samples);

                    for (final double u : underlyings) {
                        for (final double q : qRates) {
                            for (final double r : rRates) {
                                for (final double v : vols) {
                                    spot.setValue(u);
                                    qRate.setValue(q);
                                    rRate.setValue(r);
                                    vol.setValue(v);

                                    expected.clear();
                                    calculated.clear();

                                    final double refNPV = refOption.NPV();
                                    final double optNPV = option.NPV();

                                    expected.put("value", refNPV);
                                    calculated.put("value", optNPV);

                                    if (testGreeks && option.NPV() > spot.value() * 1.0e-5) {
                                        expected.put("delta", refOption.delta());
                                        expected.put("gamma", refOption.gamma());
                                        expected.put("theta", refOption.theta());
                                        calculated.put("delta", option.delta());
                                        calculated.put("gamma", option.gamma());
                                        calculated.put("theta", option.theta());
                                    }

                                    for (final Entry<String, Double> entry : calculated.entrySet()) {
                                        final String greek = entry.getKey();
                                        final double expct = expected.get(greek), calcl = calculated.get(greek), tol = tolerance.get(greek);
                                        final double error = Utilities.relativeError(expct, calcl, u);
                                        if (error > tol) {
                                            REPORT_FAILURE(greek, payoff, exercise, u, q, r, today, v, expct, calcl, error, tol);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    @Test
    public void testJRBinomialEngines() {

        QL.info("Testing JR binomial European engines against analytic results...");

        final EngineType engine = EngineType.JR;
        final int timeSteps = 251;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 0.002);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testCRRBinomialEngines() {

        QL.info("Testing CRR binomial European engines against analytic results...");

        final EngineType engine = EngineType.CRR;
        final int timeSteps = 501;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 0.002);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testEQPBinomialEngines() {

        QL.info("Testing EQP binomial European engines against analytic results...");

        final EngineType engine = EngineType.EQP;
        final int timeSteps = 501;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 0.02);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testTGEOBinomialEngines() {

        QL.info("Testing TGEO binomial European engines against analytic results...");

        final EngineType engine = EngineType.TGEO;
        final int timeSteps = 251;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 0.002);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testTIANBinomialEngines() {

        QL.info("Testing TIAN binomial European engines against analytic results...");

        final EngineType engine = EngineType.TIAN;
        final int timeSteps = 251;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 0.002);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testLRBinomialEngines() {

        QL.info("Testing LR binomial European engines against analytic results...");

        final EngineType engine = EngineType.LR;
        final int timeSteps = 251;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 1.0e-6);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testJOSHIBinomialEngines() {

        QL.info("Testing Joshi binomial European engines against analytic results...");

        final EngineType engine = EngineType.JOSHI;
        final int timeSteps = 251;
        final int samples = 0;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 1.0e-7);
        relativeTol.put("delta", 1.0e-3);
        relativeTol.put("gamma", 1.0e-4);
        relativeTol.put("theta", 0.03);
        testEngineConsistency(engine, timeSteps, samples, relativeTol, true);
    }


    @Test
    public void testFdEngines() {

        QL.info("Testing finite-differences European engines against analytic results...");

        final EngineType engine = EngineType.FiniteDifferences;
        final @NonNegative int timeSteps = 300;
        final @NonNegative int gridPoints = 300;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(4);
        relativeTol.put("value", 1.0e-4);
        relativeTol.put("delta", 1.0e-6);
        relativeTol.put("gamma", 1.0e-6);
        relativeTol.put("theta", 1.0e-4);
        testEngineConsistency(engine, timeSteps, gridPoints, relativeTol, true);
    }


    @Test
    public void testIntegralEngines() {

        QL.info("Testing integral engines against analytic results...");


        final EngineType engine = EngineType.Integral;
        final int timeSteps = 300;
        final int gridPoints = 300;
        final Map<String,Double> relativeTol = new HashMap<String, Double>(1);
        relativeTol.put("value", 0.0001);
        testEngineConsistency(engine, timeSteps, gridPoints, relativeTol);
    }


    //
    //  void EuropeanOptionTest::testMcEngines() {
    //
    //      BOOST_MESSAGE("Testing Monte Carlo European engines "
    //                    "against analytic results...");
    //
    //      EngineType engine = PseudoMonteCarlo;
    //      Size steps = Null<Size>();
    //      Size samples = 40000;
    //      std::map<std::string,Real> relativeTol;
    //      relativeTol["value"] = 0.01;
    //      testEngineConsistency(engine,steps,samples,relativeTol);
    //  }

    //  void EuropeanOptionTest::testQmcEngines() {
    //
    //      BOOST_MESSAGE("Testing Quasi Monte Carlo European engines "
    //                    "against analytic results...");
    //
    //      EngineType engine = QuasiMonteCarlo;
    //      Size steps = Null<Size>();
    //      Size samples = 4095; // 2^12-1
    //      std::map<std::string,Real> relativeTol;
    //      relativeTol["value"] = 0.01;
    //      testEngineConsistency(engine,steps,samples,relativeTol);
    //  }

    //  void EuropeanOptionTest::testPriceCurve() {
    //
    //      BOOST_MESSAGE("Testing European price curves...");
    //
    //      /* The data below are from
    //         "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
    //      */
    //      EuropeanOptionData values[] = {
    //        // pag 2-8
    //        //        type, strike,   spot,    q,    r,    t,  vol,   value
    //        { Option.Type.Call,  65.00,  60.00, 0.00, 0.08, 0.25, 0.30,  2.1334, 0.0},
    //        { Option.Type.Put,   95.00, 100.00, 0.05, 0.10, 0.50, 0.20,  2.4648, 0.0},
    //      };
    //
    //      DayCounter dc = Actual360();
    //      Date today = Date::todaysDate();
    //      Size timeSteps = 300;
    //      Size gridPoints = 300;
    //
    //      boost::shared_ptr<SimpleQuote> spot(new SimpleQuote(0.0));
    //      boost::shared_ptr<SimpleQuote> qRate(new SimpleQuote(0.0));
    //      boost::shared_ptr<YieldTermStructure> qTS = flatRate(today, qRate, dc);
    //      boost::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0));
    //      boost::shared_ptr<YieldTermStructure> rTS = flatRate(today, rRate, dc);
    //      boost::shared_ptr<SimpleQuote> vol(new SimpleQuote(0.0));
    //      boost::shared_ptr<BlackVolTermStructure> volTS = flatVol(today, vol, dc);
    //      boost::shared_ptr<PricingEngine>
    //          engine(new FDEuropeanEngine(timeSteps, gridPoints));
    //
    //      for (Size i=0; i<LENGTH(values); i++) {
    //
    //          boost::shared_ptr<StrikedTypePayoff> payoff(new
    //              PlainVanillaPayoff(values[i].type, values[i].strike));
    //          Date exDate = today + timeToDays(values[i].t);
    //          boost::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate));
    //
    //          spot ->setValue(values[i].s);
    //          qRate->setValue(values[i].q);
    //          rRate->setValue(values[i].r);
    //          vol  ->setValue(values[i].v);
    //
    //          boost::shared_ptr<StochasticProcess> stochProcess(new
    //              BlackScholesMertonProcess(Handle<Quote>(spot),
    //                                        Handle<YieldTermStructure>(qTS),
    //                                        Handle<YieldTermStructure>(rTS),
    //                                        Handle<BlackVolTermStructure>(volTS)));
    //
    //          EuropeanOption option(stochProcess, payoff, exercise, engine);
    //          SampledCurve price_curve = option.result<SampledCurve>("priceCurve");
    //          if (price_curve.empty()) {
    //              REPORT_FAILURE("no price curve", payoff, exercise, values[i].s,
    //                             values[i].q, values[i].r, today,
    //                             values[i].v, values[i].result, 0.0,
    //                             0.0, 0.0);
    //              continue;
    //          }
    //
    //          // Ignore the end points
    //          Size start = price_curve.size() / 4;
    //          Size end = price_curve.size() * 3 / 4;
    //          for (Size i=start; i < end; i++) {
    //              spot->setValue(price_curve.gridValue(i));
    //              boost::shared_ptr<StochasticProcess> stochProcess1(
    //                        new BlackScholesMertonProcess(
    //                                         Handle<Quote>(spot),
    //                                         Handle<YieldTermStructure>(qTS),
    //                                         Handle<YieldTermStructure>(rTS),
    //                                         Handle<BlackVolTermStructure>(volTS)));
    //
    //              EuropeanOption option1(stochProcess, payoff, exercise, engine);
    //              Real calculated = option1.NPV();
    //              Real error = std::fabs(calculated-price_curve.value(i));
    //              Real tolerance = 1e-3;
    //              if (error>tolerance) {
    //                  REPORT_FAILURE("price curve error", payoff, exercise,
    //                                 price_curve.gridValue(i),
    //                                 values[i].q, values[i].r, today,
    //                                 values[i].v,
    //                                 price_curve.value(i), calculated,
    //                                 error, tolerance);
    //                  break;
    //              }
    //          }
    //      }
    //
    //  }


    private void REPORT_FAILURE(final String greekName, final StrikedTypePayoff payoff, final Exercise exercise,
            final double s, final double q, final double r, final Date today,
            final double v, final double expected, final double calculated, final double error, final double tolerance) {

        final StringBuilder sb = new StringBuilder();
        sb.append(exercise).append(" ");
        sb.append(payoff.optionType()).append(" option with ");
        sb.append(payoff.getClass().getName() + " payoff:\n");
        sb.append("    spot value:       " + s + "\n");

        sb.append("    strike:           " + payoff.strike() + "\n");
        sb.append("    dividend yield:   " + q + "\n");
        sb.append("    risk-free rate:   " + r + "\n");
        sb.append("    reference date:   " + today + "\n");
        sb.append("    maturity:         " + exercise.lastDate() + "\n");
        sb.append("    volatility:       " + v + "\n\n");
        sb.append("    expected " + greekName + ":   " + expected + "\n" );
        sb.append("    calculated " + greekName + ": " + calculated + "\n");
        sb.append("    error:            " + error + "\n");
        sb.append("    tolerance:        " + tolerance);
        fail(sb.toString());
    }

}
TOP

Related Classes of org.jquantlib.testsuite.instruments.EuropeanOptionTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.