//private double DXDstrike_; public AmericanPayoffAtExpiry(double spot, double discount, double dividendDiscount, double variance, StrikedTypePayoff payoff) { spot_ = spot; discount_ = discount; dividendDiscount_ = dividendDiscount; variance_ = variance; if (!(spot_ > 0.0)) throw new ApplicationException("positive spot_ value required"); forward_ = spot_ * dividendDiscount_ / discount_; if (!(discount_ > 0.0)) throw new ApplicationException("positive discount required"); if (!(dividendDiscount_ > 0.0)) throw new ApplicationException("positive dividend discount_ required"); if (!(variance_ >= 0.0)) throw new ApplicationException("negative variance_ not allowed"); stdDev_ = Math.Sqrt(variance_); Option.Type type = payoff.optionType(); strike_ = payoff.strike(); mu_ = Math.Log(dividendDiscount_ / discount_) / variance_ - 0.5; // binary cash-or-nothing payoff? CashOrNothingPayoff coo = payoff as CashOrNothingPayoff; if (coo != null) { K_ = coo.cashPayoff(); //DKDstrike_ = 0.0; } // binary asset-or-nothing payoff? AssetOrNothingPayoff aoo = payoff as AssetOrNothingPayoff; if (aoo != null) { K_ = forward_; //DKDstrike_ = 0.0; mu_ += 1.0; } log_H_S_ = Math.Log(strike_ / spot_); double n_d1; double n_d2; double cum_d1_; double cum_d2_; if (variance_ >= Const.QL_EPSILON) { D1_ = log_H_S_ / stdDev_ + mu_ * stdDev_; D2_ = D1_ - 2.0 * mu_ * stdDev_; CumulativeNormalDistribution f = new CumulativeNormalDistribution(); cum_d1_ = f.value(D1_); cum_d2_ = f.value(D2_); n_d1 = f.derivative(D1_); n_d2 = f.derivative(D2_); } else { if (log_H_S_ > 0) { cum_d1_ = 1.0; cum_d2_ = 1.0; } else { cum_d1_ = 0.0; cum_d2_ = 0.0; } n_d1 = 0.0; n_d2 = 0.0; } switch (type) { // up-and-in cash-(at-hit)-or-nothing option // a.k.a. american call with cash-or-nothing payoff case Option.Type.Call: if (strike_ > spot_) { alpha_ = 1.0 - cum_d2_; // N(-d2) DalphaDd1_ = -n_d2; // -n( d2) beta_ = 1.0 - cum_d1_; // N(-d1) DbetaDd2_ = -n_d1; // -n( d1) } else { alpha_ = 0.5; DalphaDd1_ = 0.0; beta_ = 0.5; DbetaDd2_ = 0.0; } break; // down-and-in cash-(at-hit)-or-nothing option // a.k.a. american put with cash-or-nothing payoff case Option.Type.Put: if (strike_ < spot_) { alpha_ = cum_d2_; // N(d2) DalphaDd1_ = n_d2; // n(d2) beta_ = cum_d1_; // N(d1) DbetaDd2_ = n_d1; // n(d1) } else { alpha_ = 0.5; DalphaDd1_ = 0.0; beta_ = 0.5; DbetaDd2_ = 0.0; } break; default: throw new ApplicationException("invalid option type"); } inTheMoney_ = (type == Option.Type.Call && strike_ < spot_) || (type == Option.Type.Put && strike_ > spot_); if (inTheMoney_) { Y_ = 1.0; X_ = 1.0; //DYDstrike_ = 0.0; //DXDstrike_ = 0.0; } else { Y_ = 1.0; X_ = Math.Pow((double)(strike_ / spot_), (double)(2.0 * mu_)); // DXDstrike_ = ......; } }
// critical commodity price //public static double criticalPrice(StrikedTypePayoff payoff, double riskFreeDiscount, double dividendDiscount, // double variance, double tolerance = 1e-6); public static double criticalPrice(StrikedTypePayoff payoff, double riskFreeDiscount, double dividendDiscount, double variance, double tolerance) { // Calculation of seed value, Si double n= 2.0*Math.Log(dividendDiscount/riskFreeDiscount)/(variance); double m=-2.0*Math.Log(riskFreeDiscount)/(variance); double bT = Math.Log(dividendDiscount/riskFreeDiscount); double qu, Su, h, Si; switch (payoff.optionType()) { case Option.Type.Call: qu = (-(n-1.0) + Math.Sqrt(((n-1.0)*(n-1.0)) + 4.0*m))/2.0; Su = payoff.strike() / (1.0 - 1.0/qu); h = -(bT + 2.0*Math.Sqrt(variance)) * payoff.strike() / (Su - payoff.strike()); Si = payoff.strike() + (Su - payoff.strike()) * (1.0 - Math.Exp(h)); break; case Option.Type.Put: qu = (-(n-1.0) - Math.Sqrt(((n-1.0)*(n-1.0)) + 4.0*m))/2.0; Su = payoff.strike() / (1.0 - 1.0/qu); h = (bT - 2.0*Math.Sqrt(variance)) * payoff.strike() / (payoff.strike() - Su); Si = Su + (payoff.strike() - Su) * Math.Exp(h); break; default: throw new ArgumentException("unknown option type"); } // Newton Raphson algorithm for finding critical price Si double Q, LHS, RHS, bi; double forwardSi = Si * dividendDiscount / riskFreeDiscount; double d1 = (Math.Log(forwardSi/payoff.strike()) + 0.5*variance) / Math.Sqrt(variance); CumulativeNormalDistribution cumNormalDist = new CumulativeNormalDistribution(); double K = (riskFreeDiscount!=1.0 ? -2.0*Math.Log(riskFreeDiscount)/ (variance*(1.0-riskFreeDiscount)) : 0.0); double temp = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance))*riskFreeDiscount; switch (payoff.optionType()) { case Option.Type.Call: Q = (-(n-1.0) + Math.Sqrt(((n-1.0)*(n-1.0)) + 4 * K)) / 2; LHS = Si - payoff.strike(); RHS = temp + (1 - dividendDiscount * cumNormalDist.value(d1)) * Si / Q; bi = dividendDiscount * cumNormalDist.value(d1) * (1 - 1 / Q) + (1 - dividendDiscount * cumNormalDist.derivative(d1) / Math.Sqrt(variance)) / Q; while (Math.Abs(LHS - RHS)/payoff.strike() > tolerance) { Si = (payoff.strike() + RHS - bi * Si) / (1 - bi); forwardSi = Si * dividendDiscount / riskFreeDiscount; d1 = (Math.Log(forwardSi/payoff.strike())+0.5*variance) /Math.Sqrt(variance); LHS = Si - payoff.strike(); double temp2 = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance))*riskFreeDiscount; RHS = temp2 + (1 - dividendDiscount * cumNormalDist.value(d1)) * Si / Q; bi = dividendDiscount * cumNormalDist.value(d1) * (1 - 1 / Q) + (1 - dividendDiscount * cumNormalDist.derivative(d1) / Math.Sqrt(variance)) / Q; } break; case Option.Type.Put: Q = (-(n-1.0) - Math.Sqrt(((n-1.0)*(n-1.0)) + 4 * K)) / 2; LHS = payoff.strike() - Si; RHS = temp - (1 - dividendDiscount * cumNormalDist.value(-d1)) * Si / Q; bi = -dividendDiscount * cumNormalDist.value(-d1) * (1 - 1 / Q) - (1 + dividendDiscount * cumNormalDist.derivative(-d1) / Math.Sqrt(variance)) / Q; while (Math.Abs(LHS - RHS)/payoff.strike() > tolerance) { Si = (payoff.strike() - RHS + bi * Si) / (1 + bi); forwardSi = Si * dividendDiscount / riskFreeDiscount; d1 = (Math.Log(forwardSi/payoff.strike())+0.5*variance) /Math.Sqrt(variance); LHS = payoff.strike() - Si; double temp2 = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance))*riskFreeDiscount; RHS = temp2 - (1 - dividendDiscount * cumNormalDist.value(-d1)) * Si / Q; bi = -dividendDiscount * cumNormalDist.value(-d1) * (1 - 1 / Q) - (1 + dividendDiscount * cumNormalDist.derivative(-d1) / Math.Sqrt(variance)) / Q; } break; default: throw new ArgumentException("unknown option type"); } return Si; }
public AmericanPayoffAtHit(double spot, double discount, double dividendDiscount, double variance, StrikedTypePayoff payoff) { spot_ = spot; discount_ = discount; dividendDiscount_ = dividendDiscount; variance_ = variance; if (!(spot_ > 0.0)) throw new ApplicationException("positive spot value required"); if (!(discount_ > 0.0)) throw new ApplicationException("positive discount required"); if (!(dividendDiscount_ > 0.0)) throw new ApplicationException("positive dividend discount required"); if (!(variance_ >= 0.0)) throw new ApplicationException("negative variance not allowed"); stdDev_ = Math.Sqrt(variance_); Option.Type type = payoff.optionType(); strike_ = payoff.strike(); log_H_S_ = Math.Log(strike_ / spot_); double n_d1; double n_d2; double cum_d1_; double cum_d2_; if (variance_ >= Const.QL_Epsilon) { if (discount_ == 0.0 && dividendDiscount_ == 0.0) { mu_ = -0.5; lambda_ = 0.5; } else if (discount_ == 0.0) { throw new ApplicationException("null discount not handled yet"); } else { mu_ = Math.Log(dividendDiscount_ / discount_) / variance_ - 0.5; lambda_ = Math.Sqrt(mu_ * mu_ - 2.0 * Math.Log(discount_) / variance_); } D1_ = log_H_S_ / stdDev_ + lambda_ * stdDev_; D2_ = D1_ - 2.0 * lambda_ * stdDev_; CumulativeNormalDistribution f = new CumulativeNormalDistribution(); cum_d1_ = f.value(D1_); cum_d2_ = f.value(D2_); n_d1 = f.derivative(D1_); n_d2 = f.derivative(D2_); } else { // not tested yet mu_ = Math.Log(dividendDiscount_ / discount_) / variance_ - 0.5; lambda_ = Math.Sqrt(mu_ * mu_ - 2.0 * Math.Log(discount_) / variance_); if (log_H_S_ > 0) { cum_d1_ = 1.0; cum_d2_ = 1.0; } else { cum_d1_ = 0.0; cum_d2_ = 0.0; } n_d1 = 0.0; n_d2 = 0.0; } switch (type) { // up-and-in cash-(at-hit)-or-nothing option // a.k.a. american call with cash-or-nothing payoff case Option.Type.Call: if (strike_ > spot_) { alpha_ = 1.0 - cum_d1_; // N(-d1) DalphaDd1_ = -n_d1; // -n( d1) beta_ = 1.0 - cum_d2_; // N(-d2) DbetaDd2_ = -n_d2; // -n( d2) } else { alpha_ = 0.5; DalphaDd1_ = 0.0; beta_ = 0.5; DbetaDd2_ = 0.0; } break; // down-and-in cash-(at-hit)-or-nothing option // a.k.a. american put with cash-or-nothing payoff case Option.Type.Put: if (strike_ < spot_) { alpha_ = cum_d1_; // N(d1) DalphaDd1_ = n_d1; // n(d1) beta_ = cum_d2_; // N(d2) DbetaDd2_ = n_d2; // n(d2) } else { alpha_ = 0.5; DalphaDd1_ = 0.0; beta_ = 0.5; DbetaDd2_ = 0.0; } break; default: throw new ApplicationException("invalid option type"); } muPlusLambda_ = mu_ + lambda_; muMinusLambda_ = mu_ - lambda_; inTheMoney_ = (type == Option.Type.Call && strike_ < spot_) || (type == Option.Type.Put && strike_ > spot_); if (inTheMoney_) { forward_ = 1.0; X_ = 1.0; DXDstrike_ = 0.0; } else { forward_ = Math.Pow(strike_ / spot_, muPlusLambda_); X_ = Math.Pow(strike_ / spot_, muMinusLambda_); // DXDstrike_ = ......; } // Binary Cash-Or-Nothing payoff? CashOrNothingPayoff coo = payoff as CashOrNothingPayoff; if (coo != null) { K_ = coo.cashPayoff(); DKDstrike_ = 0.0; } // Binary Asset-Or-Nothing payoff? AssetOrNothingPayoff aoo = payoff as AssetOrNothingPayoff; if (aoo != null) { if (inTheMoney_) { K_ = spot_; DKDstrike_ = 0.0; } else { K_ = aoo.strike(); DKDstrike_ = 1.0; } } }
// public BlackCalculator(StrikedTypePayoff payoff, double forward, double stdDev, double discount = 1.0) { public BlackCalculator(StrikedTypePayoff payoff, double forward, double stdDev, double discount) { strike_ = payoff.strike(); forward_ = forward; stdDev_ = stdDev; discount_ = discount; variance_ = stdDev*stdDev; if (!(forward>0.0)) throw new ApplicationException("positive forward value required: " + forward + " not allowed"); if (!(stdDev>=0.0)) throw new ApplicationException("non-negative standard deviation required: " + stdDev + " not allowed"); if (!(discount>0.0)) throw new ApplicationException("positive discount required: " + discount + " not allowed"); if (stdDev_>=Const.QL_EPSILON) { if (strike_==0.0) { n_d1_ = 0.0; n_d2_ = 0.0; cum_d1_ = 1.0; cum_d2_= 1.0; } else { D1_ = Math.Log(forward/strike_)/stdDev_ + 0.5*stdDev_; D2_ = D1_-stdDev_; CumulativeNormalDistribution f = new CumulativeNormalDistribution(); cum_d1_ = f.value(D1_); cum_d2_= f.value(D2_); n_d1_ = f.derivative(D1_); n_d2_ = f.derivative(D2_); } } else { if (forward>strike_) { cum_d1_ = 1.0; cum_d2_= 1.0; } else { cum_d1_ = 0.0; cum_d2_= 0.0; } n_d1_ = 0.0; n_d2_ = 0.0; } X_ = strike_; DXDstrike_ = 1.0; // the following one will probably disappear as soon as // super-share will be properly handled DXDs_ = 0.0; // this part is always executed. // in case of plain-vanilla payoffs, it is also the only part // which is executed. switch (payoff.optionType()) { case Option.Type.Call: alpha_ = cum_d1_;// N(d1) DalphaDd1_ = n_d1_;// n(d1) beta_ = -cum_d2_;// -N(d2) DbetaDd2_ = - n_d2_;// -n(d2) break; case Option.Type.Put: alpha_ = -1.0+cum_d1_;// -N(-d1) DalphaDd1_ = n_d1_;// n( d1) beta_ = 1.0-cum_d2_;// N(-d2) DbetaDd2_ = - n_d2_;// -n( d2) break; default: throw new ArgumentException("invalid option type"); } // now dispatch on type. Calculator calc = new Calculator(this); payoff.accept(calc); }
void REPORT_FAILURE(string greekName, StrikedTypePayoff payoff, Exercise exercise, double s, double q, double r, Date today, double v, double expected, double calculated, double error, double tolerance) { Assert.Fail(exercise + " " + payoff.optionType() + " option with " + payoff + " payoff:\n" + " spot value: " + s + "\n" + " strike: " + payoff.strike() + "\n" + " dividend yield: " + q + "\n" + " risk-free rate: " + r + "\n" + " reference date: " + today + "\n" + " maturity: " + exercise.lastDate() + "\n" + " volatility: " + v + "\n\n" + " expected " + greekName + ": " + expected + "\n" + " calculated " + greekName + ": " + calculated + "\n" + " error: " + error + "\n" + " tolerance: " + tolerance); }
// critical commodity price public static double criticalPrice(StrikedTypePayoff payoff, double riskFreeDiscount, double dividendDiscount, double variance, double tolerance) { // Calculation of seed value, Si double n = 2.0 * Math.Log(dividendDiscount / riskFreeDiscount) / (variance); double m = -2.0 * Math.Log(riskFreeDiscount) / (variance); double bT = Math.Log(dividendDiscount / riskFreeDiscount); double qu, Su, h, Si = 0; switch (payoff.optionType()) { case Option.Type.Call: qu = (-(n - 1.0) + Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * m)) / 2.0; Su = payoff.strike() / (1.0 - 1.0 / qu); h = -(bT + 2.0 * Math.Sqrt(variance)) * payoff.strike() / (Su - payoff.strike()); Si = payoff.strike() + (Su - payoff.strike()) * (1.0 - Math.Exp(h)); break; case Option.Type.Put: qu = (-(n - 1.0) - Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4.0 * m)) / 2.0; Su = payoff.strike() / (1.0 - 1.0 / qu); h = (bT - 2.0 * Math.Sqrt(variance)) * payoff.strike() / (payoff.strike() - Su); Si = Su + (payoff.strike() - Su) * Math.Exp(h); break; default: Utils.QL_FAIL("unknown option type"); break; } // Newton Raphson algorithm for finding critical price Si double Q, LHS, RHS, bi; double forwardSi = Si * dividendDiscount / riskFreeDiscount; double d1 = (Math.Log(forwardSi / payoff.strike()) + 0.5 * variance) / Math.Sqrt(variance); CumulativeNormalDistribution cumNormalDist = new CumulativeNormalDistribution(); double K = (!Utils.close(riskFreeDiscount, 1.0, 1000)) ? -2.0 * Math.Log(riskFreeDiscount) / (variance * (1.0 - riskFreeDiscount)) : 2.0 / variance; double temp = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance)) * riskFreeDiscount; switch (payoff.optionType()) { case Option.Type.Call: Q = (-(n - 1.0) + Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4 * K)) / 2; LHS = Si - payoff.strike(); RHS = temp + (1 - dividendDiscount * cumNormalDist.value(d1)) * Si / Q; bi = dividendDiscount * cumNormalDist.value(d1) * (1 - 1 / Q) + (1 - dividendDiscount * cumNormalDist.derivative(d1) / Math.Sqrt(variance)) / Q; while (Math.Abs(LHS - RHS) / payoff.strike() > tolerance) { Si = (payoff.strike() + RHS - bi * Si) / (1 - bi); forwardSi = Si * dividendDiscount / riskFreeDiscount; d1 = (Math.Log(forwardSi / payoff.strike()) + 0.5 * variance) / Math.Sqrt(variance); LHS = Si - payoff.strike(); double temp2 = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance)) * riskFreeDiscount; RHS = temp2 + (1 - dividendDiscount * cumNormalDist.value(d1)) * Si / Q; bi = dividendDiscount * cumNormalDist.value(d1) * (1 - 1 / Q) + (1 - dividendDiscount * cumNormalDist.derivative(d1) / Math.Sqrt(variance)) / Q; } break; case Option.Type.Put: Q = (-(n - 1.0) - Math.Sqrt(((n - 1.0) * (n - 1.0)) + 4 * K)) / 2; LHS = payoff.strike() - Si; RHS = temp - (1 - dividendDiscount * cumNormalDist.value(-d1)) * Si / Q; bi = -dividendDiscount *cumNormalDist.value(-d1) * (1 - 1 / Q) - (1 + dividendDiscount * cumNormalDist.derivative(-d1) / Math.Sqrt(variance)) / Q; while (Math.Abs(LHS - RHS) / payoff.strike() > tolerance) { Si = (payoff.strike() - RHS + bi * Si) / (1 + bi); forwardSi = Si * dividendDiscount / riskFreeDiscount; d1 = (Math.Log(forwardSi / payoff.strike()) + 0.5 * variance) / Math.Sqrt(variance); LHS = payoff.strike() - Si; double temp2 = Utils.blackFormula(payoff.optionType(), payoff.strike(), forwardSi, Math.Sqrt(variance)) * riskFreeDiscount; RHS = temp2 - (1 - dividendDiscount * cumNormalDist.value(-d1)) * Si / Q; bi = -dividendDiscount *cumNormalDist.value(-d1) * (1 - 1 / Q) - (1 + dividendDiscount * cumNormalDist.derivative(-d1) / Math.Sqrt(variance)) / Q; } break; default: Utils.QL_FAIL("unknown option type"); break; } return(Si); }
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"); StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; Utils.QL_REQUIRE(payoff != null, () => "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(); Utils.QL_REQUIRE(spot > 0.0, () => "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 = (!Utils.close(riskFreeDiscount, 1.0, 1000)) ? -2.0 * Math.Log(riskFreeDiscount) / (variance * (1.0 - riskFreeDiscount)) : 2.0 / variance; 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: Utils.QL_FAIL("unknown option type"); break; } } // end of "early exercise can be optimal" }
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"); StrikedTypePayoff payoff = arguments_.payoff as StrikedTypePayoff; Utils.QL_REQUIRE(payoff != null, () => "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(); Utils.QL_REQUIRE(spot > 0.0, () => "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 = 0; switch (payoff.optionType()) { case Option.Type.Call: phi = 1; break; case Option.Type.Put: phi = -1; break; default: Utils.QL_FAIL("invalid option type"); break; } //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" }