
/*
 Copyright (C) 2007 Richard Gomes
 Copyright (C) 2007 Tito Ingargiola

 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
 <https://www.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 examples;

import org.quantlib.QuantLib;
import org.quantlib.Actual365Fixed;
import org.quantlib.AmericanExercise;
import org.quantlib.AnalyticEuropeanEngine;
import org.quantlib.AnalyticHestonEngine;
import org.quantlib.AnalyticHestonEngine_Integration;
import org.quantlib.AnalyticPTDHestonEngine;
import org.quantlib.BaroneAdesiWhaleyApproximationEngine;
import org.quantlib.BatesEngine;
import org.quantlib.BatesModel;
import org.quantlib.BatesProcess;
import org.quantlib.BermudanExercise;
import org.quantlib.BinomialCRRVanillaEngine;
import org.quantlib.BinomialEQPVanillaEngine;
import org.quantlib.BinomialJ4VanillaEngine;
import org.quantlib.BinomialJRVanillaEngine;
import org.quantlib.BinomialLRVanillaEngine;
import org.quantlib.BinomialTianVanillaEngine;
import org.quantlib.BinomialTrigeorgisVanillaEngine;
import org.quantlib.BjerksundStenslandApproximationEngine;
import org.quantlib.BlackConstantVol;
import org.quantlib.BlackScholesMertonProcess;
import org.quantlib.BlackVolTermStructureHandle;
import org.quantlib.BoundaryConstraint;
import org.quantlib.Calendar;
import org.quantlib.ConstantParameter;
import org.quantlib.COSHestonEngine;
import org.quantlib.Date;
import org.quantlib.DateVector;
import org.quantlib.DayCounter;
import org.quantlib.EuropeanExercise;
import org.quantlib.Exercise;
import org.quantlib.FdBlackScholesVanillaEngine;
import org.quantlib.HestonModel;
import org.quantlib.HestonProcess;
import org.quantlib.FlatForward;
import org.quantlib.IntegralEngine;
import org.quantlib.MCLDEuropeanEngine;
import org.quantlib.MCPREuropeanEngine;
import org.quantlib.MCPREuropeanHestonEngine;
import org.quantlib.Month;
import org.quantlib.Option;
import org.quantlib.Payoff;
import org.quantlib.Period;
import org.quantlib.PiecewiseTimeDependentHestonModel;
import org.quantlib.PlainVanillaPayoff;
import org.quantlib.PositiveConstraint;
import org.quantlib.QuoteHandle;
import org.quantlib.Settings;
import org.quantlib.SimpleQuote;
import org.quantlib.TARGET;
import org.quantlib.TimeGrid;
import org.quantlib.TimeUnit;
import org.quantlib.VanillaOption;
import org.quantlib.YieldTermStructureHandle;

/**
 * EquityOption Test app - java version of QuantLib/Examples/EquityOption
 * to illustrate use of Quantlib through supplied SWIG interfaces.
 *
 * You need to run this with a correctly set library path and something like:
 *
 * -Djava.library.path=/usr/local/lib
 *
 * @author Richard Gomes
 * @author Tito Ingargiola
 */
public class EquityOptions {

    public static void main(String[] args) throws Exception {
        long beginTime = System.currentTimeMillis();

        // our option
        Option.Type type = Option.Type.Put;
        double strike = 40.0;
        double underlying = 36.0;
        double riskFreeRate = 0.06;
        double dividendYield = 0.00;
        double volatility = 0.2;

        Date todaysDate = new Date(15, Month.May, 1998);
        Date settlementDate = new Date(17, Month.May, 1998);
        Settings.instance().setEvaluationDate(todaysDate);

        Date maturity = new Date(17, Month.May, 1999);
        DayCounter dayCounter = new Actual365Fixed();
        Calendar calendar = new TARGET();

        // write column headings
        String fmt = "\n%-35s %-14s %-14s %-14s\n";
        System.out.printf(fmt, "Method", "European", "Bermudan", "American");
        System.out.println("============================================================================");


        // define European, Bermudan, and American exercises
        DateVector exerciseDates = new DateVector();
        for (int i = 1; i <= 4; i++) {
            Date forward = settlementDate.add(new Period(3*i, TimeUnit.Months));
            exerciseDates.add(forward);
        }
        Exercise europeanExercise = new EuropeanExercise(maturity);
        Exercise bermudanExercise = new BermudanExercise(exerciseDates);
        Exercise americanExercise = new AmericanExercise(settlementDate,
                                                         maturity);


        // define the underlying asset and the yield/dividend/volatility curves
        QuoteHandle underlyingH = new QuoteHandle(new SimpleQuote(underlying));
        YieldTermStructureHandle flatTermStructure =
            new YieldTermStructureHandle(new FlatForward(
                                  settlementDate, riskFreeRate, dayCounter));
        YieldTermStructureHandle flatDividendYield =
            new YieldTermStructureHandle(new FlatForward(
                                  settlementDate, dividendYield, dayCounter));
        BlackVolTermStructureHandle flatVolatility =
            new BlackVolTermStructureHandle(new BlackConstantVol(
                           settlementDate, calendar, volatility, dayCounter));

        BlackScholesMertonProcess stochasticProcess =
            new BlackScholesMertonProcess(underlyingH,
                                          flatDividendYield,
                                          flatTermStructure,
                                          flatVolatility);

        // options
        PlainVanillaPayoff payoff = new PlainVanillaPayoff(type, strike);

        VanillaOption europeanOption =
            new VanillaOption(payoff, europeanExercise);
        VanillaOption bermudanOption =
            new VanillaOption(payoff, bermudanExercise);
        VanillaOption americanOption =
            new VanillaOption(payoff, americanExercise);

        fmt = "%34s %13.9f %13.9f %13.9f\n";

        // Analytic formulas:

        // Black-Scholes for European
        String method = "Black-Scholes";
        europeanOption.setPricingEngine(
                               new AnalyticEuropeanEngine(stochasticProcess));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );


		// Heston
        method = "Heston Semi-Analytic";
        HestonProcess hestonProcess =
            new HestonProcess(flatTermStructure,
							  flatDividendYield,
							  underlyingH, 
							  volatility*volatility,
							  1.0,
							  volatility*volatility,
							  0.0001,
							  0.0);
		HestonModel hestonModel = new HestonModel(hestonProcess);
		europeanOption.setPricingEngine(new AnalyticHestonEngine(hestonModel));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );

        method = "Heston COS Method";                                      	
		europeanOption.setPricingEngine(new COSHestonEngine(hestonModel));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );
                                              
		method = "Heston time dependent parameter";
		europeanOption.setPricingEngine(
			new AnalyticPTDHestonEngine(
				new PiecewiseTimeDependentHestonModel(
					flatTermStructure,
					flatDividendYield,
					underlyingH, 
					volatility*volatility,
					new ConstantParameter(volatility*volatility, new PositiveConstraint()),
					new ConstantParameter(1.0, new PositiveConstraint()),
					new ConstantParameter(1e-4, new PositiveConstraint()),
					new ConstantParameter(0.0, new BoundaryConstraint(-1.0, 1.0)),
					new TimeGrid(dayCounter.yearFraction(todaysDate, maturity), 10)					
				), 
				AnalyticPTDHestonEngine.ComplexLogFormula.AndersenPiterbarg,
				AnalyticHestonEngine_Integration.gaussLaguerre(32)) );
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );
		                                              
		// Bates
        method = "Bates Semi-Analytic";
        BatesProcess batesProcess =
            new BatesProcess(flatTermStructure,
							 flatDividendYield,
							 underlyingH, 
							 volatility*volatility,
							 1.0,
							 volatility*volatility,
							 0.0001,
							 0.0,
							 1e-14, 1e-14, 1e-14);
		BatesModel batesModel = new BatesModel(batesProcess);
		europeanOption.setPricingEngine(new BatesEngine(batesModel));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );

        // Barone-Adesi and Whaley approximation for American
        method = "Barone-Adesi/Whaley";
        americanOption.setPricingEngine(
                 new BaroneAdesiWhaleyApproximationEngine(stochasticProcess));
        System.out.printf(fmt, new Object[] { method,
                                              Double.NaN,
                                              Double.NaN,
                                              americanOption.NPV() } );

        // Bjerksund and Stensland approximation for American
        method = "Bjerksund/Stensland";
        americanOption.setPricingEngine(
                new BjerksundStenslandApproximationEngine(stochasticProcess));
        System.out.printf(fmt, new Object[] { method,
                                              Double.NaN,
                                              Double.NaN,
                                              americanOption.NPV() } );

        // Integral
        method = "Integral";
        europeanOption.setPricingEngine(new IntegralEngine(stochasticProcess));
        System.out.printf(fmt, new Object[] { method, europeanOption.NPV(),
                                              Double.NaN, Double.NaN } );

        // Finite differences
        int timeSteps = 801;
        method = "Finite differences";
        europeanOption.setPricingEngine(
                             new FdBlackScholesVanillaEngine(stochasticProcess,
                                                             timeSteps,
                                                             timeSteps-1));
        bermudanOption.setPricingEngine(
                             new FdBlackScholesVanillaEngine(stochasticProcess,
                                                             timeSteps,
                                                             timeSteps-1));
        americanOption.setPricingEngine(
                             new FdBlackScholesVanillaEngine(stochasticProcess,
                                                             timeSteps,
                                                             timeSteps-1));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() });

        // Binomial method
        method = "Binomial Jarrow-Rudd";
        europeanOption.setPricingEngine(new BinomialJRVanillaEngine(
                                                    stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(new BinomialJRVanillaEngine(
                                                    stochasticProcess, timeSteps));
        americanOption.setPricingEngine(new BinomialJRVanillaEngine(
                                                    stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Binomial Cox-Ross-Rubinstein";
        europeanOption.setPricingEngine(
                   new BinomialCRRVanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
                   new BinomialCRRVanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
                   new BinomialCRRVanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Additive equiprobabilities";
        europeanOption.setPricingEngine(
             new BinomialEQPVanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
             new BinomialEQPVanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
             new BinomialEQPVanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Binomial Trigeorgis";
        europeanOption.setPricingEngine(
                          new BinomialTrigeorgisVanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
                          new BinomialTrigeorgisVanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
                          new BinomialTrigeorgisVanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Binomial Tian";
        europeanOption.setPricingEngine(
                                new BinomialTianVanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
                                new BinomialTianVanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
                                new BinomialTianVanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Binomial Leisen-Reimer";
        europeanOption.setPricingEngine(
                        new BinomialLRVanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
                        new BinomialLRVanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
                        new BinomialLRVanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );

        method = "Binomial Joshi";
        europeanOption.setPricingEngine(
                              new BinomialJ4VanillaEngine(stochasticProcess, timeSteps));
        bermudanOption.setPricingEngine(
                              new BinomialJ4VanillaEngine(stochasticProcess, timeSteps));
        americanOption.setPricingEngine(
                              new BinomialJ4VanillaEngine(stochasticProcess, timeSteps));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              bermudanOption.NPV(),
                                              americanOption.NPV() } );


        // Monte Carlo Method
        timeSteps = 1;
        int mcSeed = 42;
        int nSamples = 32768; // 2^15
        int maxSamples = 1048576; // 2^20

        method = "MC (crude)";
        europeanOption.setPricingEngine(
                  new MCPREuropeanEngine(stochasticProcess,
                                         timeSteps,
                                         QuantLib.nullInt(),
                                         false, false,
                                         nSamples, 0.02, maxSamples, mcSeed));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );

        method = "MC (Sobol)";
        europeanOption.setPricingEngine(
                  new MCLDEuropeanEngine(stochasticProcess,
                                         timeSteps,
                                         QuantLib.nullInt(),
                                         false, false,
                                         nSamples, 0.02, maxSamples, mcSeed));
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );

		method = "Heston Monte-Carlo";
		europeanOption.setPricingEngine(
			new MCPREuropeanHestonEngine(hestonProcess, 25, QuantLib.nullInt(), true, nSamples));
									   
        System.out.printf(fmt, new Object[] { method,
                                              europeanOption.NPV(),
                                              Double.NaN,
                                              Double.NaN } );
                                              
        /*
        method = "MC (Longstaff Schwartz)";
        // This is the original C++ code:
        //        MakeMCAmericanEngine<PseudoRandom>().withSteps(100)
        //        .withAntitheticVariate()
        //        .withCalibrationSamples(4096)
        //        .withTolerance(0.02)
        //        .withSeed(mcSeed);

        System.out.printf(fmt, new Object[] { method,
                                              Double.NaN,
                                              Double.NaN,
                                              americanOption.NPV() });
        */

        long msecs = (System.currentTimeMillis()-beginTime);
        System.out.println("Run completed in "+msecs+" ms.");

    }
}

