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() { 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; } }
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(); }