Inheritance: EarlyExercise
        public static Greeks GetOptionOnFutureGreeks(double underlyingPrice, double strike, double riskFreeRate,
                                                     DateTime expirationDate, DateTime calculationDate, string optionType, string exerciseType,
                                                     double optionPrice = double.NaN, double impliedVol = 0.15, string engineName = "baw")
        {
            QLNet.Date ExpirationDateObj  = new QLNet.Date(expirationDate.Day, expirationDate.Month, expirationDate.Year);
            QLNet.Date CalculationDateObj = new QLNet.Date(calculationDate.Day, calculationDate.Month, calculationDate.Year);

            QLNet.DayCounter DayCountObj = new QLNet.Actual365Fixed();
            QLNet.Calendar   CalendarObj = new QLNet.UnitedStates();

            Greeks GreeksOutput = new Greeks();

            QLNet.Option.Type OptionTypeObj;
            QLNet.Exercise    ExerciseObj;
            double            ImpliedVol;
            double            OptionPrice;

            int CalDte = DayCountObj.dayCount(CalculationDateObj, ExpirationDateObj);

            GreeksOutput.CalDte = CalDte;

            if (!double.IsNaN(optionPrice))
            {
                if (optionType.ToUpper() == "C")
                {
                    if (optionPrice + strike - underlyingPrice <= 1.0e-12)
                    {
                        GreeksOutput.Delta = 1;
                        return(GreeksOutput);
                    }
                }
                else if (optionType.ToUpper() == "P")
                {
                    if (optionPrice - strike + underlyingPrice <= 1.0e-12)
                    {
                        GreeksOutput.Delta = -1;
                        return(GreeksOutput);
                    }
                }
            }

            if (CalDte == 0)
            {
                if (optionType.ToUpper() == "C")
                {
                    if (strike <= underlyingPrice)
                    {
                        GreeksOutput.Delta = 1;
                    }
                    else
                    {
                        GreeksOutput.Delta = 0;
                    }
                }
                else if (optionType.ToUpper() == "P")
                {
                    if (strike >= underlyingPrice)
                    {
                        GreeksOutput.Delta = -1;
                    }
                    else
                    {
                        GreeksOutput.Delta = 0;
                    }
                }
                return(GreeksOutput);
            }

            if (optionType.ToUpper() == "C")
            {
                OptionTypeObj = QLNet.Option.Type.Call;
            }
            else if (optionType.ToUpper() == "P")
            {
                OptionTypeObj = QLNet.Option.Type.Put;
            }
            else
            {
                return(GreeksOutput);
            }

            if (exerciseType.ToUpper() == "E")
            {
                ExerciseObj = new QLNet.EuropeanExercise(ExpirationDateObj);
            }
            else if (exerciseType.ToUpper() == "A")
            {
                ExerciseObj = new QLNet.AmericanExercise(CalculationDateObj, ExpirationDateObj);
            }
            else
            {
                return(GreeksOutput);
            }

            QLNet.Settings.setEvaluationDate(CalculationDateObj);

            QLNet.Handle <Quote> UnderlyingObj = new QLNet.Handle <Quote>(new QLNet.SimpleQuote(underlyingPrice));
            QLNet.Handle <YieldTermStructure>    FlatRateObj  = new QLNet.Handle <YieldTermStructure>(new QLNet.FlatForward(CalculationDateObj, riskFreeRate, DayCountObj));
            QLNet.Handle <BlackVolTermStructure> FlatVolTsObj = new QLNet.Handle <BlackVolTermStructure>(new QLNet.BlackConstantVol(CalculationDateObj, CalendarObj, impliedVol, DayCountObj));

            QLNet.BlackProcess       BlackProc = new QLNet.BlackProcess(UnderlyingObj, FlatRateObj, FlatVolTsObj);
            QLNet.PlainVanillaPayoff PayoffObj = new QLNet.PlainVanillaPayoff(OptionTypeObj, strike);

            QLNet.VanillaOption OptionObj = new QLNet.VanillaOption(PayoffObj, ExerciseObj);

            if (engineName == "baw")
            {
                OptionObj.setPricingEngine(new QLNet.BaroneAdesiWhaleyApproximationEngine(BlackProc));
            }
            else if (engineName == "fda")
            {
                OptionObj.setPricingEngine(new QLNet.FDAmericanEngine(BlackProc, 100, 100));
            }
            else
            {
                return(GreeksOutput);
            }


            if (!double.IsNaN(optionPrice))
            {
                try
                {
                    ImpliedVol = OptionObj.impliedVolatility(targetValue: optionPrice, process: BlackProc, accuracy: 1e-5);
                }
                catch
                {
                    return(GreeksOutput);
                }

                FlatVolTsObj = new QLNet.Handle <BlackVolTermStructure>(new QLNet.BlackConstantVol(CalculationDateObj, CalendarObj, ImpliedVol, DayCountObj));
                BlackProc    = new QLNet.BlackProcess(UnderlyingObj, FlatRateObj, FlatVolTsObj);

                if (engineName == "baw")
                {
                    OptionObj.setPricingEngine(new QLNet.BaroneAdesiWhaleyApproximationEngine(BlackProc));
                }
                else if (engineName == "fda")
                {
                    OptionObj.setPricingEngine(new QLNet.FDAmericanEngine(BlackProc, 100, 100));
                }
                OptionPrice = optionPrice;
            }
            else
            {
                OptionPrice = OptionObj.NPV();
                ImpliedVol  = impliedVol;
            }

            OptionObj = new QLNet.VanillaOption(PayoffObj, new QLNet.EuropeanExercise(ExpirationDateObj));
            OptionObj.setPricingEngine(new QLNet.AnalyticEuropeanEngine(BlackProc));

            GreeksOutput.Delta       = OptionObj.delta();
            GreeksOutput.Vega        = OptionObj.vega();
            GreeksOutput.Theta       = OptionObj.thetaPerDay();
            GreeksOutput.Gamma       = OptionObj.gamma();
            GreeksOutput.OptionPrice = OptionPrice;
            GreeksOutput.ImpliedVol  = ImpliedVol;

            return(GreeksOutput);
        }
Example #2
0
        public override void calculate()
        {
            Utils.QL_REQUIRE(arguments_.exercise.type() == Exercise.Type.American, () => "not an American Option");

            AmericanExercise ex = arguments_.exercise as AmericanExercise;

            Utils.QL_REQUIRE(ex != null, () => "non-American exercise given");

            Utils.QL_REQUIRE(!ex.payoffAtExpiry(), () => "payoff at expiry not handled");

            PlainVanillaPayoff payoff = arguments_.payoff as PlainVanillaPayoff;

            Utils.QL_REQUIRE(payoff != null, () => "non-plain payoff given");

            double variance         = process_.blackVolatility().link.blackVariance(ex.lastDate(), payoff.strike());
            double dividendDiscount = process_.dividendYield().link.discount(ex.lastDate());
            double riskFreeDiscount = process_.riskFreeRate().link.discount(ex.lastDate());

            double spot = process_.stateVariable().link.value();

            Utils.QL_REQUIRE(spot > 0.0, () => "negative or null underlying given");

            double strike = payoff.strike();

            if (payoff.optionType() == Option.Type.Put)
            {
                // use put-call simmetry
                Utils.swap <double>(ref spot, ref strike);
                Utils.swap <double>(ref riskFreeDiscount, ref dividendDiscount);
                payoff = new PlainVanillaPayoff(Option.Type.Call, strike);
            }

            if (dividendDiscount >= 1.0)
            {
                // early exercise is never optimal - use Black formula
                double          forwardPrice = spot * dividendDiscount / riskFreeDiscount;
                BlackCalculator black        = new BlackCalculator(payoff, forwardPrice, Math.Sqrt(variance), riskFreeDiscount);

                results_.value        = black.value();
                results_.delta        = black.delta(spot);
                results_.deltaForward = black.deltaForward();
                results_.elasticity   = black.elasticity(spot);
                results_.gamma        = black.gamma(spot);

                DayCounter rfdc  = process_.riskFreeRate().link.dayCounter();
                DayCounter divdc = process_.dividendYield().link.dayCounter();
                DayCounter voldc = process_.blackVolatility().link.dayCounter();
                double     t     = rfdc.yearFraction(process_.riskFreeRate().link.referenceDate(), arguments_.exercise.lastDate());
                results_.rho = black.rho(t);

                t = divdc.yearFraction(process_.dividendYield().link.referenceDate(), arguments_.exercise.lastDate());
                results_.dividendRho = black.dividendRho(t);

                t                    = voldc.yearFraction(process_.blackVolatility().link.referenceDate(), arguments_.exercise.lastDate());
                results_.vega        = black.vega(t);
                results_.theta       = black.theta(spot, t);
                results_.thetaPerDay = black.thetaPerDay(spot, t);

                results_.strikeSensitivity  = black.strikeSensitivity();
                results_.itmCashProbability = black.itmCashProbability();
            }
            else
            {
                // early exercise can be optimal - use approximation
                results_.value = americanCallApproximation(spot, strike, riskFreeDiscount, dividendDiscount, variance);
            }
        }
        public static Greeks GetOptionOnFutureGreeks(double underlyingPrice,double strike,double riskFreeRate, 
            DateTime expirationDate, DateTime calculationDate, string optionType, string exerciseType,
            double optionPrice=double.NaN,double impliedVol=0.15,string engineName="baw")
        {
            QLNet.Date ExpirationDateObj = new QLNet.Date(expirationDate.Day, expirationDate.Month, expirationDate.Year);
            QLNet.Date CalculationDateObj = new QLNet.Date(calculationDate.Day, calculationDate.Month, calculationDate.Year);

            QLNet.DayCounter DayCountObj = new QLNet.Actual365Fixed();
            QLNet.Calendar CalendarObj = new QLNet.UnitedStates();

            Greeks GreeksOutput = new Greeks();
            QLNet.Option.Type OptionTypeObj;
            QLNet.Exercise ExerciseObj;
            double ImpliedVol;
            double OptionPrice;

            int CalDte = DayCountObj.dayCount(CalculationDateObj, ExpirationDateObj);
            GreeksOutput.CalDte = CalDte;

            if (!double.IsNaN(optionPrice))
            {
                if (optionType.ToUpper() == "C")
                {
                    if (optionPrice + strike - underlyingPrice <= 1.0e-12)
                    {
                        GreeksOutput.Delta = 1;
                        return GreeksOutput;
                    }
                }
                else if (optionType.ToUpper() == "P")
                {
                    if (optionPrice - strike + underlyingPrice <= 1.0e-12)
                    {
                        GreeksOutput.Delta = -1;
                        return GreeksOutput;
                    }
                }
            }

            if (CalDte == 0)
            {
                if (optionType.ToUpper() == "C")
                {
                    if (strike <= underlyingPrice)
                    {
                        GreeksOutput.Delta = 1;
                    }
                    else
                    {
                        GreeksOutput.Delta = 0;
                    }
                }
                else if (optionType.ToUpper() == "P")
                {
                    if (strike >= underlyingPrice)
                    {
                        GreeksOutput.Delta = -1;
                    }
                    else
                    {
                        GreeksOutput.Delta = 0;
                    }
                }
                return GreeksOutput;
            }

            if (optionType.ToUpper() == "C")
            {
                OptionTypeObj = QLNet.Option.Type.Call;
            }
            else if (optionType.ToUpper() == "P")
            {
                OptionTypeObj = QLNet.Option.Type.Put;
            }
            else
            {
                return GreeksOutput;
            }

            if (exerciseType.ToUpper() == "E")
            {
                ExerciseObj = new QLNet.EuropeanExercise(ExpirationDateObj);
            }
            else if (exerciseType.ToUpper() == "A")
            {
                ExerciseObj = new QLNet.AmericanExercise(CalculationDateObj, ExpirationDateObj);
            }
            else
            {
                return GreeksOutput;
            }

            QLNet.Settings.setEvaluationDate(CalculationDateObj);

            QLNet.Handle<Quote> UnderlyingObj = new QLNet.Handle<Quote>(new QLNet.SimpleQuote(underlyingPrice));
            QLNet.Handle<YieldTermStructure> FlatRateObj = new QLNet.Handle<YieldTermStructure>(new QLNet.FlatForward(CalculationDateObj, riskFreeRate, DayCountObj));
            QLNet.Handle<BlackVolTermStructure> FlatVolTsObj = new QLNet.Handle<BlackVolTermStructure>(new QLNet.BlackConstantVol(CalculationDateObj, CalendarObj, impliedVol, DayCountObj));

            QLNet.BlackProcess BlackProc = new QLNet.BlackProcess(UnderlyingObj, FlatRateObj, FlatVolTsObj);
            QLNet.PlainVanillaPayoff PayoffObj = new QLNet.PlainVanillaPayoff(OptionTypeObj, strike);

            QLNet.VanillaOption OptionObj = new QLNet.VanillaOption(PayoffObj, ExerciseObj);

            if (engineName == "baw")
            {
                OptionObj.setPricingEngine(new QLNet.BaroneAdesiWhaleyApproximationEngine(BlackProc));
            }
            else if (engineName == "fda")
            {
                OptionObj.setPricingEngine(new QLNet.FDAmericanEngine(BlackProc, 100, 100));
            }
            else
            {
                return GreeksOutput;
            }


            if (!double.IsNaN(optionPrice))
            {
                try
                {

                    ImpliedVol = OptionObj.impliedVolatility(targetValue:optionPrice, process:BlackProc,accuracy:1e-5);
                }
                catch
                {
                    return GreeksOutput;
                }
                
                FlatVolTsObj = new QLNet.Handle<BlackVolTermStructure>(new QLNet.BlackConstantVol(CalculationDateObj, CalendarObj, ImpliedVol, DayCountObj));
                BlackProc = new QLNet.BlackProcess(UnderlyingObj, FlatRateObj, FlatVolTsObj);

                if (engineName == "baw")
                {
                    OptionObj.setPricingEngine(new QLNet.BaroneAdesiWhaleyApproximationEngine(BlackProc));
                }
                else if (engineName == "fda")
                {
                    OptionObj.setPricingEngine(new QLNet.FDAmericanEngine(BlackProc, 100, 100));
                }
                OptionPrice = optionPrice;
            }
            else
            {
                OptionPrice = OptionObj.NPV();
                ImpliedVol = impliedVol;
            }

            OptionObj = new QLNet.VanillaOption(PayoffObj, new QLNet.EuropeanExercise(ExpirationDateObj));
            OptionObj.setPricingEngine(new QLNet.AnalyticEuropeanEngine(BlackProc));

            GreeksOutput.Delta = OptionObj.delta();
            GreeksOutput.Vega = OptionObj.vega();
            GreeksOutput.Theta = OptionObj.thetaPerDay();
            GreeksOutput.Gamma = OptionObj.gamma();
            GreeksOutput.OptionPrice = OptionPrice;
            GreeksOutput.ImpliedVol = ImpliedVol;

            return GreeksOutput;

        }
        public override void calculate()
        {
            if (arguments_.barrierType == DoubleBarrier.Type.KIKO ||
                arguments_.barrierType == DoubleBarrier.Type.KOKI)
            {
                AmericanExercise ex = arguments_.exercise as AmericanExercise;
                Utils.QL_REQUIRE(ex != null, () => "KIKO/KOKI options must have American exercise");
                Utils.QL_REQUIRE(ex.dates()[0] <=
                                 process_.blackVolatility().currentLink().referenceDate(),
                                 () => "American option with window exercise not handled yet");
            }
            else
            {
                EuropeanExercise ex = arguments_.exercise as EuropeanExercise;
                Utils.QL_REQUIRE(ex != null, () => "non-European exercise given");
            }
            CashOrNothingPayoff payoff = arguments_.payoff as CashOrNothingPayoff;

            Utils.QL_REQUIRE(payoff != null, () => "a cash-or-nothing payoff must be given");

            double spot = process_.stateVariable().currentLink().value();

            Utils.QL_REQUIRE(spot > 0.0, () => "negative or null underlying given");

            double variance =
                process_.blackVolatility().currentLink().blackVariance(
                    arguments_.exercise.lastDate(),
                    payoff.strike());
            double barrier_lo = arguments_.barrier_lo.Value;
            double barrier_hi = arguments_.barrier_hi.Value;

            DoubleBarrier.Type barrierType = arguments_.barrierType;
            Utils.QL_REQUIRE(barrier_lo > 0.0,
                             () => "positive low barrier value required");
            Utils.QL_REQUIRE(barrier_hi > 0.0,
                             () => "positive high barrier value required");
            Utils.QL_REQUIRE(barrier_lo < barrier_hi,
                             () => "barrier_lo must be < barrier_hi");
            Utils.QL_REQUIRE(barrierType == DoubleBarrier.Type.KnockIn ||
                             barrierType == DoubleBarrier.Type.KnockOut ||
                             barrierType == DoubleBarrier.Type.KIKO ||
                             barrierType == DoubleBarrier.Type.KOKI,
                             () => "Unsupported barrier type");

            // degenerate cases
            switch (barrierType)
            {
            case DoubleBarrier.Type.KnockOut:
                if (spot <= barrier_lo || spot >= barrier_hi)
                {
                    // knocked out, no value
                    results_.value = 0;
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                break;

            case DoubleBarrier.Type.KnockIn:
                if (spot <= barrier_lo || spot >= barrier_hi)
                {
                    // knocked in - pays
                    results_.value = payoff.cashPayoff();
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                break;

            case DoubleBarrier.Type.KIKO:
                if (spot >= barrier_hi)
                {
                    // knocked out, no value
                    results_.value = 0;
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                else if (spot <= barrier_lo)
                {
                    // knocked in, pays
                    results_.value = payoff.cashPayoff();
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                break;

            case DoubleBarrier.Type.KOKI:
                if (spot <= barrier_lo)
                {
                    // knocked out, no value
                    results_.value = 0;
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                else if (spot >= barrier_hi)
                {
                    // knocked in, pays
                    results_.value = payoff.cashPayoff();
                    results_.delta = 0;
                    results_.gamma = 0;
                    results_.vega  = 0;
                    results_.rho   = 0;
                    return;
                }
                break;
            }

            AnalyticDoubleBarrierBinaryEngineHelper helper = new AnalyticDoubleBarrierBinaryEngineHelper(process_,
                                                                                                         payoff, arguments_);

            switch (barrierType)
            {
            case DoubleBarrier.Type.KnockOut:
            case DoubleBarrier.Type.KnockIn:
                results_.value = helper.payoffAtExpiry(spot, variance, barrierType);
                break;

            case DoubleBarrier.Type.KIKO:
            case DoubleBarrier.Type.KOKI:
                results_.value = helper.payoffKIKO(spot, variance, barrierType);
                break;

            default:
                results_.value = null;
                break;
            }
        }
Example #5
0
        public override void calculate()
        {
            if (!(arguments_.exercise.type() == Exercise.Type.American))
            {
                throw new Exception("not an American Option");
            }

            AmericanExercise ex = arguments_.exercise as AmericanExercise;

            if (ex == null)
            {
                throw new Exception("non-American exercise given");
            }

            if (ex.payoffAtExpiry())
            {
                throw new Exception("payoff at expiry not handled");
            }

            StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff;

            if (payoff == null)
            {
                throw new Exception("non-striked payoff given");
            }

            double variance         = process_.blackVolatility().link.blackVariance(ex.lastDate(), payoff.strike());
            double dividendDiscount = process_.dividendYield().link.discount(ex.lastDate());
            double riskFreeDiscount = process_.riskFreeRate().link.discount(ex.lastDate());
            double spot             = process_.stateVariable().link.value();

            if (!(spot > 0.0))
            {
                throw new Exception("negative or null underlying given");
            }

            double          forwardPrice = spot * dividendDiscount / riskFreeDiscount;
            BlackCalculator black        = new BlackCalculator(payoff, forwardPrice, Math.Sqrt(variance), riskFreeDiscount);

            if (dividendDiscount >= 1.0 && payoff.optionType() == Option.Type.Call)
            {
                // early exercise never optimal
                results_.value        = black.value();
                results_.delta        = black.delta(spot);
                results_.deltaForward = black.deltaForward();
                results_.elasticity   = black.elasticity(spot);
                results_.gamma        = black.gamma(spot);

                DayCounter rfdc  = process_.riskFreeRate().link.dayCounter();
                DayCounter divdc = process_.dividendYield().link.dayCounter();
                DayCounter voldc = process_.blackVolatility().link.dayCounter();
                double     t     = rfdc.yearFraction(process_.riskFreeRate().link.referenceDate(), arguments_.exercise.lastDate());
                results_.rho = black.rho(t);

                t = divdc.yearFraction(process_.dividendYield().link.referenceDate(), arguments_.exercise.lastDate());
                results_.dividendRho = black.dividendRho(t);

                t                    = voldc.yearFraction(process_.blackVolatility().link.referenceDate(), arguments_.exercise.lastDate());
                results_.vega        = black.vega(t);
                results_.theta       = black.theta(spot, t);
                results_.thetaPerDay = black.thetaPerDay(spot, t);

                results_.strikeSensitivity  = black.strikeSensitivity();
                results_.itmCashProbability = black.itmCashProbability();
            }
            else
            {
                // early exercise can be optimal
                CumulativeNormalDistribution cumNormalDist = new CumulativeNormalDistribution();
                NormalDistribution           normalDist    = new NormalDistribution();

                double tolerance = 1e-6;
                double Sk        = BaroneAdesiWhaleyApproximationEngine.criticalPrice(payoff, riskFreeDiscount, dividendDiscount, variance, tolerance);

                double forwardSk = Sk * dividendDiscount / riskFreeDiscount;

                double alpha = -2.0 * Math.Log(riskFreeDiscount) / (variance);
                double beta  = 2.0 * Math.Log(dividendDiscount / riskFreeDiscount) / (variance);
                double h     = 1 - riskFreeDiscount;
                double phi;
                switch (payoff.optionType())
                {
                case Option.Type.Call:
                    phi = 1;
                    break;

                case Option.Type.Put:
                    phi = -1;
                    break;

                default:
                    throw new ArgumentException("invalid option type");
                }
                //it can throw: to be fixed
                // FLOATING_POINT_EXCEPTION
                double temp_root    = Math.Sqrt((beta - 1) * (beta - 1) + (4 * alpha) / h);
                double lambda       = (-(beta - 1) + phi * temp_root) / 2;
                double lambda_prime = -phi * alpha / (h * h * temp_root);

                double black_Sk = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSk, Math.Sqrt(variance)) * riskFreeDiscount;
                double hA       = phi * (Sk - payoff.strike()) - black_Sk;

                double d1_Sk = (Math.Log(forwardSk / payoff.strike()) + 0.5 * variance) / Math.Sqrt(variance);
                double d2_Sk = d1_Sk - Math.Sqrt(variance);
                double part1 = forwardSk * normalDist.value(d1_Sk) / (alpha * Math.Sqrt(variance));
                double part2 = -phi *forwardSk *cumNormalDist.value(phi *d1_Sk) * Math.Log(dividendDiscount) / Math.Log(riskFreeDiscount);

                double part3 = +phi *payoff.strike() * cumNormalDist.value(phi * d2_Sk);

                double V_E_h = part1 + part2 + part3;

                double b = (1 - h) * alpha * lambda_prime / (2 * (2 * lambda + beta - 1));
                double c = -((1 - h) * alpha / (2 * lambda + beta - 1)) * (V_E_h / (hA) + 1 / h + lambda_prime / (2 * lambda + beta - 1));
                double temp_spot_ratio = Math.Log(spot / Sk);
                double chi             = temp_spot_ratio * (b * temp_spot_ratio + c);

                if (phi * (Sk - spot) > 0)
                {
                    results_.value = black.value() + hA * Math.Pow((spot / Sk), lambda) / (1 - chi);
                }
                else
                {
                    results_.value = phi * (spot - payoff.strike());
                }

                double temp_chi_prime   = (2 * b / spot) * Math.Log(spot / Sk);
                double chi_prime        = temp_chi_prime + c / spot;
                double chi_double_prime = 2 * b / (spot * spot) - temp_chi_prime / spot - c / (spot * spot);
                results_.delta = phi * dividendDiscount * cumNormalDist.value(phi * d1_Sk) + (lambda / (spot * (1 - chi)) + chi_prime / ((1 - chi) * (1 - chi))) * (phi * (Sk - payoff.strike()) - black_Sk) * Math.Pow((spot / Sk), lambda);

                results_.gamma = phi * dividendDiscount * normalDist.value(phi * d1_Sk) / (spot * Math.Sqrt(variance)) + (2 * lambda * chi_prime / (spot * (1 - chi) * (1 - chi)) + 2 * chi_prime * chi_prime / ((1 - chi) * (1 - chi) * (1 - chi)) + chi_double_prime / ((1 - chi) * (1 - chi)) + lambda * (1 - lambda) / (spot * spot * (1 - chi))) * (phi * (Sk - payoff.strike()) - black_Sk) * Math.Pow((spot / Sk), lambda);
            }             // end of "early exercise can be optimal"
        }
        public override void calculate()
        {
            AmericanExercise ex = arguments_.exercise as AmericanExercise;

            Utils.QL_REQUIRE(ex != null, () => "non-American exercise given");
            Utils.QL_REQUIRE(ex.payoffAtExpiry(), () => "payoff must be at expiry");
            Utils.QL_REQUIRE(ex.dates()[0] <= process_.blackVolatility().link.referenceDate(), () =>
                             "American option with window exercise not handled yet");

            StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff;

            Utils.QL_REQUIRE(payoff != null, () => "non-striked payoff given");

            double spot = process_.stateVariable().link.value();

            Utils.QL_REQUIRE(spot > 0.0, () => "negative or null underlying given");

            double variance = process_.blackVolatility().link.blackVariance(ex.lastDate(), payoff.strike());
            double?barrier  = arguments_.barrier;

            Utils.QL_REQUIRE(barrier > 0.0, () => "positive barrier value required");
            Barrier.Type barrierType = arguments_.barrierType;

            // KO degenerate cases
            if ((barrierType == Barrier.Type.DownOut && spot <= barrier) ||
                (barrierType == Barrier.Type.UpOut && spot >= barrier))
            {
                // knocked out, no value
                results_.value       = 0;
                results_.delta       = 0;
                results_.gamma       = 0;
                results_.vega        = 0;
                results_.theta       = 0;
                results_.rho         = 0;
                results_.dividendRho = 0;
                return;
            }

            // KI degenerate cases
            if ((barrierType == Barrier.Type.DownIn && spot <= barrier) ||
                (barrierType == Barrier.Type.UpIn && spot >= barrier))
            {
                // knocked in - is a digital european
                Exercise exercise = new EuropeanExercise(arguments_.exercise.lastDate());

                IPricingEngine engine = new AnalyticEuropeanEngine(process_);

                VanillaOption opt = new VanillaOption(payoff, exercise);
                opt.setPricingEngine(engine);
                results_.value       = opt.NPV();
                results_.delta       = opt.delta();
                results_.gamma       = opt.gamma();
                results_.vega        = opt.vega();
                results_.theta       = opt.theta();
                results_.rho         = opt.rho();
                results_.dividendRho = opt.dividendRho();
                return;
            }

            double riskFreeDiscount = process_.riskFreeRate().link.discount(ex.lastDate());

            AnalyticBinaryBarrierEngine_helper helper = new AnalyticBinaryBarrierEngine_helper(
                process_, payoff, ex, arguments_);

            results_.value = helper.payoffAtExpiry(spot, variance, riskFreeDiscount);
        }
        public override void calculate()
        {
            if (!(arguments_.exercise.type() == Exercise.Type.American))
            {
                throw new ApplicationException("not an American Option");
            }

            AmericanExercise ex = arguments_.exercise as AmericanExercise;

            if (ex == null)
            {
                throw new ApplicationException("non-American exercise given");
            }

            if (ex.payoffAtExpiry())
            {
                throw new ApplicationException("payoff at expiry not handled");
            }

            StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff;

            if (payoff == null)
            {
                throw new ApplicationException("non-striked payoff given");
            }

            double variance         = process_.blackVolatility().link.blackVariance(ex.lastDate(), payoff.strike());
            double dividendDiscount = process_.dividendYield().link.discount(ex.lastDate());
            double riskFreeDiscount = process_.riskFreeRate().link.discount(ex.lastDate());
            double spot             = process_.stateVariable().link.value();

            if (!(spot > 0.0))
            {
                throw new ApplicationException("negative or null underlying given");
            }
            double          forwardPrice = spot * dividendDiscount / riskFreeDiscount;
            BlackCalculator black        = new BlackCalculator(payoff, forwardPrice, Math.Sqrt(variance), riskFreeDiscount);

            if (dividendDiscount >= 1.0 && payoff.optionType() == Option.Type.Call)
            {
                // early exercise never optimal
                results_.value        = black.value();
                results_.delta        = black.delta(spot);
                results_.deltaForward = black.deltaForward();
                results_.elasticity   = black.elasticity(spot);
                results_.gamma        = black.gamma(spot);

                DayCounter rfdc  = process_.riskFreeRate().link.dayCounter();
                DayCounter divdc = process_.dividendYield().link.dayCounter();
                DayCounter voldc = process_.blackVolatility().link.dayCounter();
                double     t     = rfdc.yearFraction(process_.riskFreeRate().link.referenceDate(), arguments_.exercise.lastDate());
                results_.rho = black.rho(t);

                t = divdc.yearFraction(process_.dividendYield().link.referenceDate(), arguments_.exercise.lastDate());
                results_.dividendRho = black.dividendRho(t);

                t                    = voldc.yearFraction(process_.blackVolatility().link.referenceDate(), arguments_.exercise.lastDate());
                results_.vega        = black.vega(t);
                results_.theta       = black.theta(spot, t);
                results_.thetaPerDay = black.thetaPerDay(spot, t);

                results_.strikeSensitivity  = black.strikeSensitivity();
                results_.itmCashProbability = black.itmCashProbability();
            }
            else
            {
                // early exercise can be optimal
                CumulativeNormalDistribution cumNormalDist = new CumulativeNormalDistribution();
                double tolerance = 1e-6;
                double Sk        = criticalPrice(payoff, riskFreeDiscount,
                                                 dividendDiscount, variance, tolerance);
                double forwardSk = Sk * dividendDiscount / riskFreeDiscount;
                double d1        = (Math.Log(forwardSk / payoff.strike()) + 0.5 * variance)
                                   / Math.Sqrt(variance);
                double n = 2.0 * Math.Log(dividendDiscount / riskFreeDiscount) / variance;
                double K = -2.0 * Math.Log(riskFreeDiscount) /
                           (variance * (1.0 - riskFreeDiscount));
                double Q, a;
                switch (payoff.optionType())
                {
                case Option.Type.Call:
                    Q = (-(n - 1.0) + Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * K)) / 2.0;
                    a = (Sk / Q) * (1.0 - dividendDiscount * cumNormalDist.value(d1));
                    if (spot < Sk)
                    {
                        results_.value = black.value() +
                                         a * Math.Pow((spot / Sk), Q);
                    }
                    else
                    {
                        results_.value = spot - payoff.strike();
                    }
                    break;

                case Option.Type.Put:
                    Q = (-(n - 1.0) - Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * K)) / 2.0;
                    a = -(Sk / Q) *
                        (1.0 - dividendDiscount * cumNormalDist.value(-d1));
                    if (spot > Sk)
                    {
                        results_.value = black.value() +
                                         a * Math.Pow((spot / Sk), Q);
                    }
                    else
                    {
                        results_.value = payoff.strike() - spot;
                    }
                    break;

                default:
                    throw new ApplicationException("unknown option type");
                }
            } // end of "early exercise can be optimal"
        }
        //static void Main(string[] args) 
        //{
        //    List<double> xGrid = Enumerable.Range(0, 100).Select(x => x / 10.0).ToList();
        //    List<double> yGrid = Enumerable.Range(0, 100).Select(x => x / 10.0).ToList();

        //    //List<double> xGrid = Enumerable.Range(0, 100);
        //    CubicInterpolation cubic = new CubicInterpolation(xGrid, xGrid.Count, yGrid, 
        //                                                      CubicInterpolation.DerivativeApprox.Kruger, true,
        //                                                      CubicInterpolation.BoundaryCondition.SecondDerivative , 0.0,
        //                                                      CubicInterpolation.BoundaryCondition.SecondDerivative , 0.0);

            
        //}

        static void Main(string[] args)
        {

            DateTime timer = DateTime.Now;

            // set up dates
            Calendar calendar = new TARGET();
            Date todaysDate = new Date(15, Month.May, 1998);
            Date settlementDate = new Date(17, Month.May, 1998);
            Settings.setEvaluationDate(todaysDate);

            // our options
            Option.Type type = Option.Type.Put;
            double underlying = 36;
            double strike = 40;
            double dividendYield = 0.00;
            double riskFreeRate = 0.06;
            double volatility = 0.20;
            Date maturity = new Date(17, Month.May, 1999);
            DayCounter dayCounter = new Actual365Fixed();

            Console.WriteLine("Option type = " + type);
            Console.WriteLine("Maturity = " + maturity);
            Console.WriteLine("Underlying price = " + underlying);
            Console.WriteLine("Strike = " + strike);
            Console.WriteLine("Risk-free interest rate = {0:0.000000%}", riskFreeRate);
            Console.WriteLine("Dividend yield = {0:0.000000%}", dividendYield);
            Console.WriteLine("Volatility = {0:0.000000%}", volatility);
            Console.Write("\n");

            string method;

            Console.Write("\n");

            // write column headings
            int[] widths = new int[] { 35, 14, 14, 14 };
            Console.Write("{0,-" + widths[0] + "}", "Method");
            Console.Write("{0,-" + widths[1] + "}", "European");
            Console.Write("{0,-" + widths[2] + "}", "Bermudan");
            Console.WriteLine("{0,-" + widths[3] + "}", "American");

            List<Date> exerciseDates = new List<Date>(); ;
            for (int i = 1; i <= 4; i++)
                exerciseDates.Add(settlementDate + new Period(3 * i, TimeUnit.Months));

            Exercise europeanExercise = new EuropeanExercise(maturity);
            Exercise bermudanExercise = new BermudanExercise(exerciseDates);
            Exercise americanExercise = new AmericanExercise(settlementDate, maturity);

            Handle<Quote> underlyingH = new Handle<Quote>(new SimpleQuote(underlying));

            // bootstrap the yield/dividend/vol curves
            var flatTermStructure = new Handle<YieldTermStructure>(new FlatForward(settlementDate, riskFreeRate, dayCounter));
            var flatDividendTS = new Handle<YieldTermStructure>(new FlatForward(settlementDate, dividendYield, dayCounter));
            var flatVolTS = new Handle<BlackVolTermStructure>(new BlackConstantVol(settlementDate, calendar, volatility, dayCounter));
            StrikedTypePayoff payoff = new PlainVanillaPayoff(type, strike);
            var bsmProcess = new BlackScholesMertonProcess(underlyingH, flatDividendTS, flatTermStructure, flatVolTS);

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


            // Analytic formulas:

            // Black-Scholes for European
            method = "Black-Scholes";
            europeanOption.setPricingEngine(new AnalyticEuropeanEngine(bsmProcess));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + "}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + "}", "N/A");

            europeanOption.theta();

            // Barone-Adesi and Whaley approximation for American
            method = "Barone-Adesi/Whaley";
            americanOption.setPricingEngine(new BaroneAdesiWhaleyApproximationEngine(bsmProcess));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + "}", "N/A");
            Console.Write("{0,-" + widths[2] + "}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());


            // Bjerksund and Stensland approximation for American
            method = "Bjerksund/Stensland";
            americanOption.setPricingEngine(new BjerksundStenslandApproximationEngine(bsmProcess));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + "}", "N/A");
            Console.Write("{0,-" + widths[2] + "}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Integral
            method = "Integral";
            europeanOption.setPricingEngine(new IntegralEngine(bsmProcess));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + "}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + "}", "N/A");


            // Finite differences
            int timeSteps = 801;
            method = "Finite differences";
            europeanOption.setPricingEngine(new FDEuropeanEngine(bsmProcess, timeSteps, timeSteps - 1));
            bermudanOption.setPricingEngine(new FDBermudanEngine(bsmProcess, timeSteps, timeSteps - 1));
            americanOption.setPricingEngine(new FDAmericanEngine(bsmProcess, timeSteps, timeSteps - 1));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Jarrow-Rudd
            method = "Binomial Jarrow-Rudd";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());


            method = "Binomial Cox-Ross-Rubinstein";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Additive equiprobabilities
            method = "Additive equiprobabilities";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Binomial Trigeorgis
            method = "Binomial Trigeorgis";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Binomial Tian
            method = "Binomial Tian";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Binomial Leisen-Reimer
            method = "Binomial Leisen-Reimer";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // Binomial method: Binomial Joshi
            method = "Binomial Joshi";
            europeanOption.setPricingEngine(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps));
            bermudanOption.setPricingEngine(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps));
            americanOption.setPricingEngine(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps));

            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", bermudanOption.NPV());
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());


            // Monte Carlo Method: MC (crude)
            timeSteps = 1;
            method = "MC (crude)";
            ulong mcSeed = 42;
            IPricingEngine mcengine1 = new MakeMCEuropeanEngine<PseudoRandom>(bsmProcess)
                                            .withSteps(timeSteps)
                                            .withAbsoluteTolerance(0.02)
                                            .withSeed(mcSeed)
                                            .value();
            europeanOption.setPricingEngine(mcengine1);
            // Real errorEstimate = europeanOption.errorEstimate();
            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", "N/A");


            // Monte Carlo Method: QMC (Sobol)
            method = "QMC (Sobol)";
            int nSamples = 32768;  // 2^15

            IPricingEngine mcengine2 = new MakeMCEuropeanEngine<LowDiscrepancy>(bsmProcess)
                                            .withSteps(timeSteps)
                                            .withSamples(nSamples)
                                            .value();
            europeanOption.setPricingEngine(mcengine2);
            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", europeanOption.NPV());
            Console.Write("{0,-" + widths[2] + ":0.000000}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", "N/A");

            // Monte Carlo Method: MC (Longstaff Schwartz)
            method = "MC (Longstaff Schwartz)";
            IPricingEngine mcengine3 = new MakeMCAmericanEngine<PseudoRandom>(bsmProcess)
                                        .withSteps(100)
                                        .withAntitheticVariate()
                                        .withCalibrationSamples(4096)
                                        .withAbsoluteTolerance(0.02)
                                        .withSeed(mcSeed)
                                        .value();
            americanOption.setPricingEngine(mcengine3);
            Console.Write("{0,-" + widths[0] + "}", method);
            Console.Write("{0,-" + widths[1] + ":0.000000}", "N/A");
            Console.Write("{0,-" + widths[2] + ":0.000000}", "N/A");
            Console.WriteLine("{0,-" + widths[3] + ":0.000000}", americanOption.NPV());

            // End test
            Console.WriteLine(" \nRun completed in {0}", DateTime.Now - timer);
            Console.WriteLine();

            Console.Write("Press any key to continue ...");
            Console.ReadKey();
        }