public void testDiscretizationError()
        {
            // Testing the discretization error of the Heston Hull-White process
            DayCounter dc    = new Actual360();
            Date       today = Date.Today;

            Settings.Instance.setEvaluationDate(today);

            // construct a strange yield curve to check drifts and discounting
            // of the joint stochastic process

            List <Date>   dates = new List <Date>();
            List <double> times = new List <double>();
            List <double> rates = new List <double>(), divRates = new List <double>();

            for (int i = 0; i <= 31; ++i)
            {
                dates.Add(today + new Period(i, TimeUnit.Years));
                // FLOATING_POINT_EXCEPTION
                rates.Add(0.04 + 0.0001 * Math.Exp(Math.Sin(i)));
                divRates.Add(0.04 + 0.0001 * Math.Exp(Math.Sin(i)));
                times.Add(dc.yearFraction(today, dates.Last()));
            }

            Date   maturity = today + new Period(10, TimeUnit.Years);
            double v        = 0.25;

            Handle <Quote> s0  = new Handle <Quote>(new SimpleQuote(100));
            SimpleQuote    vol = new SimpleQuote(v);
            Handle <BlackVolTermStructure> volTS = new Handle <BlackVolTermStructure>(Utilities.flatVol(today, vol, dc));
            Handle <YieldTermStructure>    rTS   = new Handle <YieldTermStructure>(new InterpolatedZeroCurve <Linear>(dates, rates, dc));
            Handle <YieldTermStructure>    qTS   = new Handle <YieldTermStructure>(new InterpolatedZeroCurve <Linear>(dates, divRates, dc));

            BlackScholesMertonProcess bsmProcess = new BlackScholesMertonProcess(s0, qTS, rTS, volTS);

            HestonProcess hestonProcess = new HestonProcess(rTS, qTS, s0, v * v, 1, v * v, 1e-6, -0.4);

            HullWhiteForwardProcess hwProcess = new HullWhiteForwardProcess(rTS, 0.01, 0.01);

            hwProcess.setForwardMeasureTime(20.1472222222222222);

            double tol = 0.05;

            double[] corr   = { -0.85, 0.5 };
            double[] strike = { 50, 100, 125 };

            for (int i = 0; i < corr.Length; ++i)
            {
                for (int j = 0; j < strike.Length; ++j)
                {
                    StrikedTypePayoff payoff   = new PlainVanillaPayoff(Option.Type.Put, strike[j]);
                    Exercise          exercise = new EuropeanExercise(maturity);

                    VanillaOption optionBsmHW = new VanillaOption(payoff, exercise);
                    HullWhite     hwModel     = new HullWhite(rTS, hwProcess.a(), hwProcess.sigma());
                    optionBsmHW.setPricingEngine(new AnalyticBSMHullWhiteEngine(corr[i], bsmProcess, hwModel));

                    double expected = optionBsmHW.NPV();

                    VanillaOption optionHestonHW = new VanillaOption(payoff, exercise);
                    HybridHestonHullWhiteProcess jointProcess = new HybridHestonHullWhiteProcess(hestonProcess,
                                                                                                 hwProcess, corr[i]);
                    optionHestonHW.setPricingEngine(
                        new MakeMCHestonHullWhiteEngine <PseudoRandom, Statistics>(jointProcess)
                        .withSteps(1)
                        .withAntitheticVariate()
                        .withAbsoluteTolerance(tol)
                        .withSeed(42).getAsPricingEngine());

                    double calculated = optionHestonHW.NPV();
                    double error      = optionHestonHW.errorEstimate();

                    if ((Math.Abs(calculated - expected) > 3 * error &&
                         Math.Abs(calculated - expected) > 1e-5))
                    {
                        QAssert.Fail("Failed to reproduce discretization error"
                                     + "\n   corr:       " + corr[i]
                                     + "\n   strike:     " + strike[j]
                                     + "\n   calculated: " + calculated
                                     + "\n   error:      " + error
                                     + "\n   expected:   " + expected);
                    }
                }
            }
        }
        public void testAnalyticHestonHullWhitePricing()
        {
            // Testing analytic Heston Hull-White option pricing
            DayCounter dc    = new Actual360();
            Date       today = Date.Today;

            Settings.Instance.setEvaluationDate(today);

            // construct a strange yield curve to check drifts and discounting
            // of the joint stochastic process

            List <Date>   dates = new List <Date>();
            List <double> times = new List <double>();
            List <double> rates = new List <double>(), divRates = new List <double>();

            for (int i = 0; i <= 40; ++i)
            {
                dates.Add(today + new Period(i, TimeUnit.Years));
                // FLOATING_POINT_EXCEPTION
                rates.Add(0.03 + 0.0001 * Math.Exp(Math.Sin(i / 4.0)));
                divRates.Add(0.02 + 0.0002 * Math.Exp(Math.Sin(i / 3.0)));
                times.Add(dc.yearFraction(today, dates.Last()));
            }

            Date           maturity         = today + new Period(5, TimeUnit.Years);
            Handle <Quote> s0               = new Handle <Quote>(new SimpleQuote(100));
            Handle <YieldTermStructure> rTS = new Handle <YieldTermStructure>(new InterpolatedZeroCurve <Linear>(dates, rates, dc));
            Handle <YieldTermStructure> qTS = new Handle <YieldTermStructure>(new InterpolatedZeroCurve <Linear>(dates, divRates, dc));

            HestonProcess hestonProcess = new HestonProcess(rTS, qTS, s0, 0.08, 1.5, 0.0625, 0.5, -0.8);
            HestonModel   hestonModel   = new HestonModel(hestonProcess);

            HullWhiteForwardProcess hwFwdProcess = new HullWhiteForwardProcess(rTS, 0.01, 0.01);

            hwFwdProcess.setForwardMeasureTime(dc.yearFraction(today, maturity));
            HullWhite hullWhiteModel = new HullWhite(rTS, hwFwdProcess.a(), hwFwdProcess.sigma());

            double tol = 0.002;

            double[]      strike = { 80, 120 };
            Option.Type[] types  = { Option.Type.Put, Option.Type.Call };

            for (int i = 0; i < types.Length; ++i)
            {
                for (int j = 0; j < strike.Length; ++j)
                {
                    HybridHestonHullWhiteProcess jointProcess = new HybridHestonHullWhiteProcess(hestonProcess,
                                                                                                 hwFwdProcess, 0.0, HybridHestonHullWhiteProcess.Discretization.Euler);

                    StrikedTypePayoff payoff   = new PlainVanillaPayoff(types[i], strike[j]);
                    Exercise          exercise = new EuropeanExercise(maturity);

                    VanillaOption optionHestonHW = new VanillaOption(payoff, exercise);
                    optionHestonHW.setPricingEngine(new MakeMCHestonHullWhiteEngine <PseudoRandom, Statistics>(jointProcess)
                                                    .withSteps(1)
                                                    .withAntitheticVariate()
                                                    .withControlVariate()
                                                    .withAbsoluteTolerance(tol)
                                                    .withSeed(42).getAsPricingEngine());

                    VanillaOption optionPureHeston = new VanillaOption(payoff, exercise);
                    optionPureHeston.setPricingEngine(new AnalyticHestonHullWhiteEngine(hestonModel, hullWhiteModel, 128));

                    double calculated = optionHestonHW.NPV();
                    double error      = optionHestonHW.errorEstimate();
                    double expected   = optionPureHeston.NPV();

                    if (Math.Abs(calculated - expected) > 3 * error &&
                        Math.Abs(calculated - expected) > tol)
                    {
                        QAssert.Fail("Failed to reproduce hw heston vanilla prices"
                                     + "\n   strike:     " + strike[j]
                                     + "\n   calculated: " + calculated
                                     + "\n   error:      " + error
                                     + "\n   expected:   " + expected);
                    }
                }
            }
        }