void DayCounterTest::testThirty360_BondBasis() { BOOST_MESSAGE("Testing thirty/360 day counter (Bond Basis)..."); // http://www.isda.org/c_and_a/docs/30-360-2006ISDADefs.xls // Source: 2006 ISDA Definitions, Sec. 4.16 (f) // 30/360 (or Bond Basis) DayCounter dayCounter = Thirty360(Thirty360::BondBasis); std::vector<Date> testStartDates; std::vector<Date> testEndDates; Time calculated; // ISDA - Example 1: End dates do not involve the last day of February testStartDates.push_back(Date(20, August, 2006)); testEndDates.push_back(Date(20, February, 2007)); testStartDates.push_back(Date(20, February, 2007)); testEndDates.push_back(Date(20, August, 2007)); testStartDates.push_back(Date(20, August, 2007)); testEndDates.push_back(Date(20, February, 2008)); testStartDates.push_back(Date(20, February, 2008)); testEndDates.push_back(Date(20, August, 2008)); testStartDates.push_back(Date(20, August, 2008)); testEndDates.push_back(Date(20, February, 2009)); testStartDates.push_back(Date(20, February, 2009)); testEndDates.push_back(Date(20, August, 2009)); // ISDA - Example 2: End dates include some end-February dates testStartDates.push_back(Date(31, August, 2006)); testEndDates.push_back(Date(28, February, 2007)); testStartDates.push_back(Date(28, February, 2007)); testEndDates.push_back(Date(31, August, 2007)); testStartDates.push_back(Date(31, August, 2007)); testEndDates.push_back(Date(29, February, 2008)); testStartDates.push_back(Date(29, February, 2008)); testEndDates.push_back(Date(31, August, 2008)); testStartDates.push_back(Date(31, August, 2008)); testEndDates.push_back(Date(28, February, 2009)); testStartDates.push_back(Date(28, February, 2009)); testEndDates.push_back(Date(31, August, 2009)); //// ISDA - Example 3: Miscellaneous calculations testStartDates.push_back(Date(31, January, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(30, January, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(28, February, 2006)); testEndDates.push_back(Date(3, March, 2006)); testStartDates.push_back(Date(14, February, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(30, September, 2006)); testEndDates.push_back(Date(31, October, 2006)); testStartDates.push_back(Date(31, October, 2006)); testEndDates.push_back(Date(28, November, 2006)); testStartDates.push_back(Date(31, August, 2007)); testEndDates.push_back(Date(28, February, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(28, August, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(30, August, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(31, August, 2008)); testStartDates.push_back(Date(26, February, 2007)); testEndDates.push_back(Date(28, February, 2008)); testStartDates.push_back(Date(26, February, 2007)); testEndDates.push_back(Date(29, February, 2008)); testStartDates.push_back(Date(29, February, 2008)); testEndDates.push_back(Date(28, February, 2009)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(30, March, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(31, March, 2008)); int expected[] = { 180, 180, 180, 180, 180, 180, 178, 183, 179, 182, 178, 183, 28, 28, 5, 14, 30, 28, 178, 180, 182, 183, 362, 363, 359, 32, 33}; for (Size i = 0; i < testStartDates.size(); i++) { calculated = dayCounter.dayCount(testStartDates[i], testEndDates[i]); if (calculated != expected[i]) { BOOST_ERROR("from " << testStartDates[i] << " to " << testEndDates[i] << ":\n" << " calculated: " << calculated << "\n" << " expected: " << expected[i-1]); } } }
void AnalyticDividendEuropeanEngine::calculate() const { QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not an European option"); boost::shared_ptr<StrikedTypePayoff> payoff = boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-striked payoff given"); Date settlementDate = process_->riskFreeRate()->referenceDate(); Real riskless = 0.0; Size i; for (i=0; i<arguments_.cashFlow.size(); i++) if (arguments_.cashFlow[i]->date() >= settlementDate) riskless += arguments_.cashFlow[i]->amount() * process_->riskFreeRate()->discount( arguments_.cashFlow[i]->date()); Real spot = process_->stateVariable()->value() - riskless; QL_REQUIRE(spot > 0.0, "negative or null underlying after subtracting dividends"); DiscountFactor dividendDiscount = process_->dividendYield()->discount( arguments_.exercise->lastDate()); DiscountFactor riskFreeDiscount = process_->riskFreeRate()->discount(arguments_.exercise->lastDate()); Real forwardPrice = spot * dividendDiscount / riskFreeDiscount; Real variance = process_->blackVolatility()->blackVariance( arguments_.exercise->lastDate(), payoff->strike()); BlackCalculator black(payoff, forwardPrice, std::sqrt(variance), riskFreeDiscount); results_.value = black.value(); results_.delta = black.delta(spot); results_.gamma = black.gamma(spot); DayCounter rfdc = process_->riskFreeRate()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); Time t = voldc.yearFraction( process_->blackVolatility()->referenceDate(), arguments_.exercise->lastDate()); results_.vega = black.vega(t); Real delta_theta = 0.0, delta_rho = 0.0; for (i = 0; i < arguments_.cashFlow.size(); i++) { Date d = arguments_.cashFlow[i]->date(); if (d >= settlementDate) { delta_theta -= arguments_.cashFlow[i]->amount() * process_->riskFreeRate()->zeroRate(d,rfdc,Continuous,Annual)* process_->riskFreeRate()->discount(d); Time t = process_->time(d); delta_rho += arguments_.cashFlow[i]->amount() * t * process_->riskFreeRate()->discount(t); } } t = process_->time(arguments_.exercise->lastDate()); try { results_.theta = black.theta(spot, t) + delta_theta * black.delta(spot); } catch (Error&) { results_.theta = Null<Real>(); } results_.rho = black.rho(t) + delta_rho * black.delta(spot); }
inline bool operator==(const DayCounter& d1, const DayCounter& d2) { return (d1.empty() && d2.empty()) || (!d1.empty() && !d2.empty() && d1.name() == d2.name()); }
void BinomialVanillaEngine<T>::calculate() const { DayCounter rfdc = process_->riskFreeRate()->dayCounter(); DayCounter divdc = process_->dividendYield()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); Calendar volcal = process_->blackVolatility()->calendar(); Real s0 = process_->stateVariable()->value(); QL_REQUIRE(s0 > 0.0, "negative or null underlying given"); Volatility v = process_->blackVolatility()->blackVol( arguments_.exercise->lastDate(), s0); Date maturityDate = arguments_.exercise->lastDate(); Rate r = process_->riskFreeRate()->zeroRate(maturityDate, rfdc, Continuous, NoFrequency); Rate q = process_->dividendYield()->zeroRate(maturityDate, divdc, Continuous, NoFrequency); Date referenceDate = process_->riskFreeRate()->referenceDate(); // binomial trees with constant coefficient Handle<YieldTermStructure> flatRiskFree( boost::shared_ptr<YieldTermStructure>( new FlatForward(referenceDate, r, rfdc))); Handle<YieldTermStructure> flatDividends( boost::shared_ptr<YieldTermStructure>( new FlatForward(referenceDate, q, divdc))); Handle<BlackVolTermStructure> flatVol( boost::shared_ptr<BlackVolTermStructure>( new BlackConstantVol(referenceDate, volcal, v, voldc))); boost::shared_ptr<PlainVanillaPayoff> payoff = boost::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-plain payoff given"); Time maturity = rfdc.yearFraction(referenceDate, maturityDate); boost::shared_ptr<StochasticProcess1D> bs( new GeneralizedBlackScholesProcess( process_->stateVariable(), flatDividends, flatRiskFree, flatVol)); TimeGrid grid(maturity, timeSteps_); boost::shared_ptr<T> tree(new T(bs, maturity, timeSteps_, payoff->strike())); boost::shared_ptr<BlackScholesLattice<T> > lattice( new BlackScholesLattice<T>(tree, r, maturity, timeSteps_)); DiscretizedVanillaOption option(arguments_, *process_, grid); option.initialize(lattice, maturity); // Partial derivatives calculated from various points in the // binomial tree (Odegaard) // Rollback to third-last step, and get underlying price (s2) & // option values (p2) at this point option.rollback(grid[2]); Array va2(option.values()); QL_ENSURE(va2.size() == 3, "Expect 3 nodes in grid at second step"); Real p2h = va2[2]; // high-price Real s2 = lattice->underlying(2, 2); // high price // Rollback to second-last step, and get option value (p1) at // this point option.rollback(grid[1]); Array va(option.values()); QL_ENSURE(va.size() == 2, "Expect 2 nodes in grid at first step"); Real p1 = va[1]; // Finally, rollback to t=0 option.rollback(0.0); Real p0 = option.presentValue(); Real s1 = lattice->underlying(1, 1); // Calculate partial derivatives Real delta0 = (p1-p0)/(s1-s0); // dp/ds Real delta1 = (p2h-p1)/(s2-s1); // dp/ds // Store results results_.value = p0; results_.delta = delta0; results_.gamma = 2.0*(delta1-delta0)/(s2-s0); //d(delta)/ds results_.theta = blackScholesTheta(process_, results_.value, results_.delta, results_.gamma); }
void ContinuousArithmeticAsianLevyEngine::calculate() const { QL_REQUIRE(arguments_.averageType == Average::Arithmetic, "not an Arithmetic average option"); QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not an European Option"); DayCounter rfdc = process_->riskFreeRate()->dayCounter(); DayCounter divdc = process_->dividendYield()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); Real spot = process_->stateVariable()->value(); // payoff boost::shared_ptr<StrikedTypePayoff> payoff = boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-plain payoff given"); // original time to maturity Date maturity = arguments_.exercise->lastDate(); Time T = rfdc.yearFraction(startDate_, arguments_.exercise->lastDate()); // remaining time to maturity Time T2 = rfdc.yearFraction(process_->riskFreeRate()->referenceDate(), arguments_.exercise->lastDate()); Real strike = payoff->strike(); Volatility volatility = process_->blackVolatility()->blackVol(maturity, strike); CumulativeNormalDistribution N; Rate riskFreeRate = process_->riskFreeRate()-> zeroRate(maturity, rfdc, Continuous, NoFrequency); Rate dividendYield = process_->dividendYield()-> zeroRate(maturity, divdc, Continuous, NoFrequency); Real b = riskFreeRate - dividendYield; QL_REQUIRE(b != 0.0, "null cost of carry not allowed by Levy engine"); Real Se = (spot/(T*b))*(exp((b-riskFreeRate)*T2)-exp(-riskFreeRate*T2)); Real X; if (T2 < T) { QL_REQUIRE(!currentAverage_.empty() && currentAverage_->isValid(), "current average required"); X = strike - ((T-T2)/T)*currentAverage_->value(); } else { X = strike; } Real M = (2*spot*spot/(b+volatility*volatility)) * (((exp((2*b+volatility*volatility)*T2)-1) / (2*b+volatility*volatility))-((exp(b*T2)-1)/b)); Real D = M/(T*T); Real V = log(D)-2*(riskFreeRate*T2+log(Se)); Real d1 = (1/sqrt(V))*((log(D)/2)-log(X)); Real d2 = d1-sqrt(V); if(payoff->optionType()==Option::Call) results_.value = Se*N(d1) - X*exp(-riskFreeRate*T2)*N(d2); else results_.value = Se*N(d1) - X*exp(-riskFreeRate*T2)*N(d2) - Se + X*exp(-riskFreeRate*T2); }
void MargrabeOptionTest::testGreeks() { BOOST_MESSAGE("Testing analytic European exchange option greeks..."); SavedSettings backup; std::map<std::string,Real> calculated, expected, tolerance; tolerance["delta1"] = 1.0e-5; tolerance["delta2"] = 1.0e-5; tolerance["gamma1"] = 1.0e-5; tolerance["gamma2"] = 1.0e-5; tolerance["theta"] = 1.0e-5; tolerance["rho"] = 1.0e-5; Option::Type types[] = {Option::Call}; Real underlyings1[] = { 22.0 }; Real underlyings2[] = { 20.0 }; Rate qRates1[] = { 0.06, 0.16, 0.04 }; Rate qRates2[] = { 0.04, 0.14, 0.02 }; Rate rRates[] = { 0.1, 0.2, 0.08 }; Time residualTimes[] = { 0.1, 0.5 }; Volatility vols1[] = { 0.20 }; Volatility vols2[] = { 0.15, 0.20, 0.25}; DayCounter dc = Actual360(); Date today = Date::todaysDate(); Settings::instance().evaluationDate() = today; boost::shared_ptr<SimpleQuote> spot1(new SimpleQuote(0.0)); boost::shared_ptr<SimpleQuote> spot2(new SimpleQuote(0.0)); boost::shared_ptr<SimpleQuote> qRate1(new SimpleQuote(0.0)); boost::shared_ptr<YieldTermStructure> qTS1 = flatRate(qRate1, dc); boost::shared_ptr<SimpleQuote> qRate2(new SimpleQuote(0.0)); boost::shared_ptr<YieldTermStructure> qTS2 = flatRate(qRate2, dc); boost::shared_ptr<SimpleQuote> rRate(new SimpleQuote(0.0)); boost::shared_ptr<YieldTermStructure> rTS = flatRate(rRate, dc); boost::shared_ptr<SimpleQuote> vol1(new SimpleQuote(0.0)); boost::shared_ptr<BlackVolTermStructure> volTS1 = flatVol(vol1, dc); boost::shared_ptr<SimpleQuote> vol2(new SimpleQuote(0.0)); boost::shared_ptr<BlackVolTermStructure> volTS2 = flatVol(vol2, dc); for (Size i=0; i<LENGTH(types); i++) { for (Size k=0; k<LENGTH(residualTimes); k++) { Date exDate = today + timeToDays(residualTimes[k]); boost::shared_ptr<Exercise> exercise(new EuropeanExercise(exDate)); // option to check boost::shared_ptr<BlackScholesMertonProcess> stochProcess1(new BlackScholesMertonProcess(Handle<Quote>(spot1), Handle<YieldTermStructure>(qTS1), Handle<YieldTermStructure>(rTS), Handle<BlackVolTermStructure>(volTS1))); boost::shared_ptr<BlackScholesMertonProcess> stochProcess2(new BlackScholesMertonProcess(Handle<Quote>(spot2), Handle<YieldTermStructure>(qTS2), Handle<YieldTermStructure>(rTS), Handle<BlackVolTermStructure>(volTS2))); std::vector<boost::shared_ptr<StochasticProcess1D> > procs; procs.push_back(stochProcess1); procs.push_back(stochProcess2); //The correlation -0.5 can be different real between -1 and 1 for more tests Real correlation = -0.5; Matrix correlationMatrix(2,2, correlation); for (Integer j=0; j < 2; j++) { correlationMatrix[j][j] = 1.0; boost::shared_ptr<PricingEngine> engine( new AnalyticEuropeanMargrabeEngine(stochProcess1, stochProcess2, correlation)); //The quantities of S1 and S2 can be different from 1 & 1 for more tests MargrabeOption margrabeOption(1, 1, exercise); // analytic engine margrabeOption.setPricingEngine(engine); for (Size l=0; l<LENGTH(underlyings1); l++) { for (Size m=0; m<LENGTH(qRates1); m++) { for (Size n=0; n<LENGTH(rRates); n++) { for (Size p=0; p<LENGTH(vols1); p++) { Real u1 = underlyings1[l], u2 = underlyings2[l], u; Rate q1 = qRates1[m], q2 = qRates2[m], r = rRates[n]; Volatility v1 = vols1[p], v2 = vols2[p]; spot1 ->setValue(u1); spot2 ->setValue(u2); qRate1->setValue(q1); qRate2->setValue(q2); rRate ->setValue(r); vol1 ->setValue(v1); vol2 ->setValue(v2); Real value = margrabeOption.NPV(); calculated["delta1"] = margrabeOption.delta1(); calculated["delta2"] = margrabeOption.delta2(); calculated["gamma1"] = margrabeOption.gamma1(); calculated["gamma2"] = margrabeOption.gamma2(); calculated["theta"] = margrabeOption.theta(); calculated["rho"] = margrabeOption.rho(); if (value > spot1->value()*1.0e-5) { // perturb spot and get delta1 and gamma u = u1; Real du = u*1.0e-4; spot1->setValue(u+du); Real value_p = margrabeOption.NPV(), delta_p = margrabeOption.delta1(); spot1->setValue(u-du); Real value_m = margrabeOption.NPV(), delta_m = margrabeOption.delta1(); spot1->setValue(u); expected["delta1"] = (value_p - value_m)/(2*du); expected["gamma1"] = (delta_p - delta_m)/(2*du); u = u2; spot2->setValue(u+du); value_p = margrabeOption.NPV(); delta_p = margrabeOption.delta2(); spot2->setValue(u-du); value_m = margrabeOption.NPV(); delta_m = margrabeOption.delta2(); spot2->setValue(u); expected["delta2"] = (value_p - value_m)/(2*du); expected["gamma2"] = (delta_p - delta_m)/(2*du); // perturb rates and get rho Spread dr = r*1.0e-4; rRate->setValue(r+dr); value_p = margrabeOption.NPV(); rRate->setValue(r-dr); value_m = margrabeOption.NPV(); rRate->setValue(r); expected["rho"] = (value_p - value_m)/(2*dr); // perturb date and get theta Time dT = dc.yearFraction(today-1, today+1); Settings::instance().evaluationDate() = today-1; value_m = margrabeOption.NPV(); Settings::instance().evaluationDate() = today+1; value_p = margrabeOption.NPV(); Settings::instance().evaluationDate() = today; expected["theta"] = (value_p - value_m)/dT; // compare std::map<std::string,Real>::iterator it; for (it = calculated.begin(); it != calculated.end(); ++it) { std::string greek = it->first; Real expct = expected [greek], calcl = calculated[greek], tol = tolerance [greek]; Real error = relativeError(expct,calcl,u1); if (error>tol) { REPORT_FAILURE2(greek, exercise, u1, u2, q1, q2, r, today, v1, v2, expct, calcl, error, tol); } } } } } } } } } } }
void AnalyticKouEuropeanEngine::calculate() const { QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not an European option"); boost::shared_ptr<StrikedTypePayoff> payoff = boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-striked payoff given"); /* *calculate to be implemented */ Option::Type type = payoff->optionType(); Real eta1 = process_->posJumpMean(); QL_REQUIRE(eta1 > 1, "invalid eta1 input!!"); Real eta2 = process_->negJumpMean(); QL_REQUIRE(eta2 > 0, "invalid eta2 input!!"); Real lambda = process_->jumpIntensity(); Real p = process_->posProbability(); //here assume the three term structure have the same daycounter DayCounter daycounter = process_->riskFreeRate()->dayCounter(); Real sigma = process_->blackVolatility()->blackVol(arguments_.exercise->lastDate(), payoff->strike()); Real variance = sigma*sigma; // the paper ignore the case when dividends are payed out Real effectiveR = process_->riskFreeRate()->zeroRate(arguments_.exercise->lastDate(), daycounter, Continuous) - process_->dividendYield()->zeroRate(arguments_.exercise->lastDate(), daycounter, Continuous); Real S = process_->stateVariable()->value(); QL_REQUIRE(S > 0.0, "negative or null underlying given"); Real K = payoff->strike(); Time T = daycounter.yearFraction(process_->riskFreeRate()->referenceDate(), arguments_.exercise->lastDate()); /* *auxillary variables */ Real zeta = p*eta1 / (eta1 - 1) + (1 - p)*eta2 / (eta2 + 1) - 1; Real adjustEta1 = eta1 - 1; Real adjustEta2 = eta2 + 1; Real adjustLambda = process_->jumpIntensity()*(zeta + 1); Real effectiveP = p / (1 + zeta)*eta1 / (eta1 - 1); //price of call option Real price = S*process_->dividendYield()->discount(arguments_.exercise->lastDate()) *KouHelper::Gamma(effectiveR + 0.5*sigma*sigma - lambda*zeta, sigma, adjustLambda, effectiveP, adjustEta1, adjustEta2, log(K / S), T, tolerance_) - K*process_->riskFreeRate()->discount(arguments_.exercise->lastDate()) *KouHelper::Gamma(effectiveR - 0.5*sigma*sigma - lambda*zeta, sigma, lambda, p, eta1, eta2, log(K / S), T, tolerance_); if (type == Option::Type::Call) { results_.value = price; } else results_.value = price - S*process_->dividendYield()->discount(arguments_.exercise->lastDate()) + K*process_->riskFreeRate()->discount(arguments_.exercise->lastDate()); //more results if needed }
void SwingOptionTest::testExtOUJumpSwingOption() { BOOST_TEST_MESSAGE("Testing simple swing option pricing for Kluge model..."); SavedSettings backup; Date settlementDate = Date::todaysDate(); Settings::instance().evaluationDate() = settlementDate; DayCounter dayCounter = ActualActual(); Date maturityDate = settlementDate + Period(12, Months); boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(Option::Put, 30)); std::vector<Date> exerciseDates(1, settlementDate+Period(1, Months)); while (exerciseDates.back() < maturityDate) { exerciseDates.push_back(exerciseDates.back()+Period(1, Months)); } boost::shared_ptr<SwingExercise> swingExercise( new SwingExercise(exerciseDates)); std::vector<Time> exerciseTimes(exerciseDates.size()); for (Size i=0; i < exerciseTimes.size(); ++i) { exerciseTimes[i] = dayCounter.yearFraction(settlementDate, exerciseDates[i]); } TimeGrid grid(exerciseTimes.begin(), exerciseTimes.end(), 60); std::vector<Size> exerciseIndex(exerciseDates.size()); for (Size i=0; i < exerciseIndex.size(); ++i) { exerciseIndex[i] = grid.closestIndex(exerciseTimes[i]); } boost::shared_ptr<ExtOUWithJumpsProcess> jumpProcess = createKlugeProcess(); const Rate irRate = 0.1; boost::shared_ptr<YieldTermStructure> rTS( flatRate(settlementDate, irRate, dayCounter)); boost::shared_ptr<PricingEngine> swingEngine( new FdSimpleExtOUJumpSwingEngine(jumpProcess, rTS, 25, 50, 25)); boost::shared_ptr<PricingEngine> vanillaEngine( new FdExtOUJumpVanillaEngine(jumpProcess, rTS, 25, 50, 25)); VanillaOption bermudanOption(payoff, swingExercise); bermudanOption.setPricingEngine(vanillaEngine); const Real bermudanOptionPrices = bermudanOption.NPV(); const Size nrTrails = 16000; typedef PseudoRandom::rsg_type rsg_type; typedef MultiPathGenerator<rsg_type>::sample_type sample_type; rsg_type rsg = PseudoRandom::make_sequence_generator( jumpProcess->factors()*(grid.size()-1), BigNatural(421)); MultiPathGenerator<rsg_type> generator(jumpProcess, grid, rsg, false); for (Size i=0; i < exerciseDates.size(); ++i) { const Size exerciseRights = i+1; VanillaSwingOption swingOption(payoff, swingExercise, exerciseRights, exerciseRights); swingOption.setPricingEngine(swingEngine); const Real swingOptionPrice = swingOption.NPV(); const Real upperBound = exerciseRights*bermudanOptionPrices; if (swingOptionPrice - upperBound > 2e-2) { BOOST_ERROR("Failed to reproduce upper bounds" << "\n upper Bound: " << upperBound << "\n Price: " << swingOptionPrice); } Real lowerBound = 0.0; for (Size j=exerciseDates.size()-i-1; j < exerciseDates.size(); ++j) { VanillaOption europeanOption(payoff, boost::shared_ptr<Exercise>( new EuropeanExercise(exerciseDates[j]))); europeanOption.setPricingEngine( boost::shared_ptr<PricingEngine>(vanillaEngine)); lowerBound += europeanOption.NPV(); } if (lowerBound - swingOptionPrice > 2e-2) { BOOST_ERROR("Failed to reproduce lower bounds" << "\n lower Bound: " << lowerBound << "\n Price: " << swingOptionPrice); } // use MC plus perfect forecast to find an upper bound GeneralStatistics npv; for (Size n=0; n < nrTrails; ++n) { sample_type path = generator.next(); std::vector<Real> exerciseValues(exerciseTimes.size()); for (Size k=0; k < exerciseTimes.size(); ++k) { const Real x = path.value[0][exerciseIndex[k]]; const Real y = path.value[1][exerciseIndex[k]]; const Real s = std::exp(x+y); exerciseValues[k] =(*payoff)(s)*rTS->discount(exerciseDates[k]); } std::sort(exerciseValues.begin(), exerciseValues.end(), std::greater<Real>()); Real npCashFlows = std::accumulate(exerciseValues.begin(), exerciseValues.begin()+exerciseRights, 0.0); npv.add(npCashFlows); } const Real mcUpperBound = npv.mean(); const Real mcErrorUpperBound = npv.errorEstimate(); if (swingOptionPrice - mcUpperBound > 2.36*mcErrorUpperBound) { BOOST_ERROR("Failed to reproduce mc upper bounds" << "\n mc upper Bound: " << mcUpperBound << "\n Price: " << swingOptionPrice); } } }
void DefaultProbabilityCurveTest::testDefaultProbability() { BOOST_TEST_MESSAGE("Testing default-probability structure..."); Real hazardRate = 0.0100; Handle<Quote> hazardRateQuote = Handle<Quote>( boost::shared_ptr<Quote>(new SimpleQuote(hazardRate))); DayCounter dayCounter = Actual360(); Calendar calendar = TARGET(); Size n = 20; double tolerance = 1.0e-10; Date today = Settings::instance().evaluationDate(); Date startDate = today; Date endDate = startDate; FlatHazardRate flatHazardRate(startDate, hazardRateQuote, dayCounter); for(Size i=0; i<n; i++){ startDate = endDate; endDate = calendar.advance(endDate, 1, Years); Probability pStart = flatHazardRate.defaultProbability(startDate); Probability pEnd = flatHazardRate.defaultProbability(endDate); Probability pBetweenComputed = flatHazardRate.defaultProbability(startDate, endDate); Probability pBetween = pEnd - pStart; if (std::fabs(pBetween - pBetweenComputed) > tolerance) BOOST_ERROR( "Failed to reproduce probability(d1, d2) " << "for default probability structure\n" << std::setprecision(12) << " calculated probability: " << pBetweenComputed << "\n" << " expected probability: " << pBetween); Time t2 = dayCounter.yearFraction(today, endDate); Probability timeProbability = flatHazardRate.defaultProbability(t2); Probability dateProbability = flatHazardRate.defaultProbability(endDate); if (std::fabs(timeProbability - dateProbability) > tolerance) BOOST_ERROR( "single-time probability and single-date probability do not match\n" << std::setprecision(10) << " time probability: " << timeProbability << "\n" << " date probability: " << dateProbability); Time t1 = dayCounter.yearFraction(today, startDate); timeProbability = flatHazardRate.defaultProbability(t1, t2); dateProbability = flatHazardRate.defaultProbability(startDate, endDate); if (std::fabs(timeProbability - dateProbability) > tolerance) BOOST_ERROR( "double-time probability and double-date probability do not match\n" << std::setprecision(10) << " time probability: " << timeProbability << "\n" << " date probability: " << dateProbability); } }
void BatesModelTest::testAnalyticVsBlack() { BOOST_MESSAGE("Testing analytic Bates engine against Black formula..."); SavedSettings backup; Date settlementDate = Date::todaysDate(); Settings::instance().evaluationDate() = settlementDate; DayCounter dayCounter = ActualActual(); Date exerciseDate = settlementDate + 6*Months; boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(Option::Put, 30)); boost::shared_ptr<Exercise> exercise(new EuropeanExercise(exerciseDate)); Handle<YieldTermStructure> riskFreeTS(flatRate(0.1, dayCounter)); Handle<YieldTermStructure> dividendTS(flatRate(0.04, dayCounter)); Handle<Quote> s0(boost::shared_ptr<Quote>(new SimpleQuote(32.0))); Real yearFraction = dayCounter.yearFraction(settlementDate, exerciseDate); Real forwardPrice = s0->value()*std::exp((0.1-0.04)*yearFraction); Real expected = blackFormula(payoff->optionType(), payoff->strike(), forwardPrice, std::sqrt(0.05*yearFraction)) * std::exp(-0.1*yearFraction); const Real v0 = 0.05; const Real kappa = 5.0; const Real theta = 0.05; const Real sigma = 1.0e-4; const Real rho = 0.0; const Real lambda = 0.0001; const Real nu = 0.0; const Real delta = 0.0001; VanillaOption option(payoff, exercise); boost::shared_ptr<BatesProcess> process( new BatesProcess(riskFreeTS, dividendTS, s0, v0, kappa, theta, sigma, rho, lambda, nu, delta)); boost::shared_ptr<PricingEngine> engine(new BatesEngine( boost::shared_ptr<BatesModel>(new BatesModel(process)), 64)); option.setPricingEngine(engine); Real calculated = option.NPV(); Real tolerance = 2.0e-7; Real error = std::fabs(calculated - expected); if (error > tolerance) { BOOST_ERROR("failed to reproduce Black price with BatesEngine" << QL_FIXED << "\n calculated: " << calculated << "\n expected: " << expected << QL_SCIENTIFIC << "\n error: " << error); } engine = boost::shared_ptr<PricingEngine>(new BatesDetJumpEngine( boost::shared_ptr<BatesDetJumpModel>( new BatesDetJumpModel( process, 1.0, 0.0001)), 64)); option.setPricingEngine(engine); calculated = option.NPV(); error = std::fabs(calculated - expected); if (error > tolerance) { BOOST_ERROR("failed to reproduce Black price with " \ "BatesDetJumpEngine" << QL_FIXED << "\n calculated: " << calculated << "\n expected: " << expected << QL_SCIENTIFIC << "\n error: " << error); } engine = boost::shared_ptr<PricingEngine>(new BatesDoubleExpEngine( boost::shared_ptr<BatesDoubleExpModel>( new BatesDoubleExpModel(process, 0.0001, 0.0001, 0.0001)), 64)); option.setPricingEngine(engine); calculated = option.NPV(); error = std::fabs(calculated - expected); if (error > tolerance) { BOOST_ERROR("failed to reproduce Black price with BatesDoubleExpEngine" << QL_FIXED << "\n calculated: " << calculated << "\n expected: " << expected << QL_SCIENTIFIC << "\n error: " << error); } engine = boost::shared_ptr<PricingEngine>(new BatesDoubleExpDetJumpEngine( boost::shared_ptr<BatesDoubleExpDetJumpModel>( new BatesDoubleExpDetJumpModel( process, 0.0001, 0.0001, 0.0001, 0.5, 1.0, 0.0001)), 64)); option.setPricingEngine(engine); calculated = option.NPV(); error = std::fabs(calculated - expected); if (error > tolerance) { BOOST_ERROR("failed to reproduce Black price with " \ "BatesDoubleExpDetJumpEngine" << QL_FIXED << "\n calculated: " << calculated << "\n expected: " << expected << QL_SCIENTIFIC << "\n error: " << error); } }
void FdHullWhiteSwaptionEngine::calculate() const { const Handle<YieldTermStructure> ts = model_->termStructure(); // 1. Layout const boost::shared_ptr<FdmLinearOpLayout> layout( new FdmLinearOpLayout(std::vector<Size>(1u, xGrid_))); // 2. Mesher const DayCounter dc = ts->dayCounter(); const Date referenceDate = ts->referenceDate(); const Time maturity = dc.yearFraction(referenceDate, arguments_.exercise->lastDate()); const boost::shared_ptr<OrnsteinUhlenbeckProcess> process( new OrnsteinUhlenbeckProcess(model_->a(), model_->sigma())); const boost::shared_ptr<Fdm1dMesher> shortRateMesher( new FdmSimpleProcess1dMesher(xGrid_, process, maturity,1,invEps_)); const boost::shared_ptr<FdmMesher> mesher( new FdmMesherComposite(layout, std::vector<boost::shared_ptr<Fdm1dMesher> >( 1, shortRateMesher))); // 3. Inner Value Calculator const std::vector<Date>& exerciseDates = arguments_.exercise->dates(); std::map<Time, Date> t2d; for (Size i=0; i < exerciseDates.size(); ++i) { const Time t = dc.yearFraction(referenceDate, exerciseDates[i]); QL_REQUIRE(t >= 0, "exercise dates must not contain past date"); t2d[t] = exerciseDates[i]; } const Handle<YieldTermStructure> disTs = model_->termStructure(); const Handle<YieldTermStructure> fwdTs = arguments_.swap->iborIndex()->forwardingTermStructure(); QL_REQUIRE(fwdTs->dayCounter() == disTs->dayCounter(), "day counter of forward and discount curve must match"); QL_REQUIRE(fwdTs->referenceDate() == disTs->referenceDate(), "reference date of forward and discount curve must match"); const boost::shared_ptr<HullWhite> fwdModel( new HullWhite(fwdTs, model_->a(), model_->sigma())); const boost::shared_ptr<FdmInnerValueCalculator> calculator( new FdmAffineModelSwapInnerValue<HullWhite>( model_.currentLink(), fwdModel, arguments_.swap, t2d, mesher, 0)); // 4. Step conditions const boost::shared_ptr<FdmStepConditionComposite> conditions = FdmStepConditionComposite::vanillaComposite( DividendSchedule(), arguments_.exercise, mesher, calculator, referenceDate, dc); // 5. Boundary conditions const std::vector<boost::shared_ptr<FdmDirichletBoundary> > boundaries; // 6. Solver FdmSolverDesc solverDesc = { mesher, boundaries, conditions, calculator, maturity, tGrid_, dampingSteps_ }; const boost::scoped_ptr<FdmHullWhiteSolver> solver( new FdmHullWhiteSolver(model_, solverDesc, schemeDesc_)); results_.value = solver->valueAt(0.0); }
void AnalyticCliquetEngine::calculate() const { QL_REQUIRE(arguments_.accruedCoupon == Null<Real>() && arguments_.lastFixing == Null<Real>(), "this engine cannot price options already started"); QL_REQUIRE(arguments_.localCap == Null<Real>() && arguments_.localFloor == Null<Real>() && arguments_.globalCap == Null<Real>() && arguments_.globalFloor == Null<Real>(), "this engine cannot price capped/floored options"); QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not an European option"); boost::shared_ptr<PercentageStrikePayoff> moneyness = boost::dynamic_pointer_cast<PercentageStrikePayoff>( arguments_.payoff); QL_REQUIRE(moneyness, "wrong payoff given"); std::vector<Date> resetDates = arguments_.resetDates; resetDates.push_back(arguments_.exercise->lastDate()); Real underlying = process_->stateVariable()->value(); QL_REQUIRE(underlying > 0.0, "negative or null underlying"); Real strike = underlying * moneyness->strike(); boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(moneyness->optionType(),strike)); results_.value = 0.0; results_.delta = results_.gamma = 0.0; results_.theta = 0.0; results_.rho = results_.dividendRho = 0.0; results_.vega = 0.0; for (Size i = 1; i < resetDates.size(); i++) { Real weight = process_->dividendYield()->discount(resetDates[i-1]); DiscountFactor discount = process_->riskFreeRate()->discount(resetDates[i]) / process_->riskFreeRate()->discount(resetDates[i-1]); DiscountFactor qDiscount = process_->dividendYield()->discount(resetDates[i]) / process_->dividendYield()->discount(resetDates[i-1]); Real forward = underlying*qDiscount/discount; Real variance = process_->blackVolatility()->blackForwardVariance( resetDates[i-1],resetDates[i],strike); BlackCalculator black(payoff, forward, std::sqrt(variance), discount); DayCounter rfdc = process_->riskFreeRate()->dayCounter(); DayCounter divdc = process_->dividendYield()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); results_.value += weight * black.value(); results_.delta += weight * (black.delta(underlying) + moneyness->strike() * discount * black.beta()); results_.gamma += 0.0; results_.theta += process_->dividendYield()->forwardRate( resetDates[i-1], resetDates[i], rfdc, Continuous, NoFrequency) * weight * black.value(); Time dt = rfdc.yearFraction(resetDates[i-1],resetDates[i]); results_.rho += weight * black.rho(dt); Time t = divdc.yearFraction( process_->dividendYield()->referenceDate(), resetDates[i-1]); dt = divdc.yearFraction(resetDates[i-1],resetDates[i]); results_.dividendRho += weight * (black.dividendRho(dt) - t * black.value()); dt = voldc.yearFraction(resetDates[i-1], resetDates[i]); results_.vega += weight * black.vega(dt); } }
void BjerksundStenslandApproximationEngine::calculate() const { QL_REQUIRE(arguments_.exercise->type() == Exercise::American, "not an American Option"); boost::shared_ptr<AmericanExercise> ex = boost::dynamic_pointer_cast<AmericanExercise>(arguments_.exercise); QL_REQUIRE(ex, "non-American exercise given"); QL_REQUIRE(!ex->payoffAtExpiry(), "payoff at expiry not handled"); boost::shared_ptr<PlainVanillaPayoff> payoff = boost::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-plain payoff given"); Real variance = process_->blackVolatility()->blackVariance(ex->lastDate(), payoff->strike()); DiscountFactor dividendDiscount = process_->dividendYield()->discount(ex->lastDate()); DiscountFactor riskFreeDiscount = process_->riskFreeRate()->discount(ex->lastDate()); Real spot = process_->stateVariable()->value(); QL_REQUIRE(spot > 0.0, "negative or null underlying given"); Real strike = payoff->strike(); if (payoff->optionType()==Option::Put) { // use put-call simmetry std::swap(spot, strike); std::swap(riskFreeDiscount, dividendDiscount); payoff = boost::shared_ptr<PlainVanillaPayoff>( new PlainVanillaPayoff(Option::Call, strike)); } if (dividendDiscount>=1.0) { // early exercise is never optimal - use Black formula Real forwardPrice = spot * dividendDiscount / riskFreeDiscount; BlackCalculator black(payoff, forwardPrice, std::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()->dayCounter(); DayCounter divdc = process_->dividendYield()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); Time t = rfdc.yearFraction(process_->riskFreeRate()->referenceDate(), arguments_.exercise->lastDate()); results_.rho = black.rho(t); t = divdc.yearFraction(process_->dividendYield()->referenceDate(), arguments_.exercise->lastDate()); results_.dividendRho = black.dividendRho(t); t = voldc.yearFraction(process_->blackVolatility()->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); } }
void qlHazardRateFactory::calculate() { try { TiXmlDocument document; document.Parse(instInfo_.c_str(), 0, TIXML_ENCODING_UTF8); TiXmlNode* rootNode = document.FirstChild("defaultCurveCalculation"); Settings::instance().evaluationDate() = strToDate(rootNode->FirstChildElement("evaluationTime")->GetText()); DayCounter daycounter = Actual365Fixed(); TiXmlElement* pricingParametorNode = rootNode->FirstChildElement("pricingParameter"); TiXmlElement* referenceCurveNode = pricingParametorNode->FirstChildElement("referenceCurves"); Calendar calendar = SouthKorea(); Date refereceDate = Settings::instance().evaluationDate(); // must be a business day refereceDate = calendar.adjust(refereceDate); // dummy curve boost::shared_ptr<Quote> flatRate(new SimpleQuote(0.01)); //Handle<YieldTermStructure> tsCurve( // boost::shared_ptr<FlatForward>( // new FlatForward(todaysDate, Handle<Quote>(flatRate), // Actual365Fixed()))); TiXmlNode* pricingParametorNode = document.FirstChild("pricingParameter"); TiXmlElement* referenceCurveNode = pricingParametorNode->FirstChildElement("referenceCurves"); std::vector<boost::shared_ptr<HazardRateStructure>> hazardRateCurves; TiXmlElement* tsNode = referenceCurveNode->FirstChildElement("termstructure"); if(tsNode) { for(tsNode; tsNode; tsNode = tsNode->NextSiblingElement("termstructure")) { hazardRateCurves.push_back(this->hazardRateTS(tsNode)); } } TiXmlNode* yieldNode = document.FirstChild("yieldNode"); qlYieldTermStructureFactory yieldTsFactory = qlYieldTermStructureFactory(); this->tsCurve_ = yieldTsFactory.yieldTSHandle(yieldNode); // outputTenor Build std::vector<Period> outputTenors; Size outputNum = outputTenors.size(); qlTimeUnitFactory tuFactory = qlTimeUnitFactory(); TiXmlElement* outputTenorNode = pricingParametorNode->FirstChildElement("outputCurveTemplate"); TiXmlElement* tenorNode = outputTenorNode->FirstChildElement("tenorList"); if(tenorNode) { for(tenorNode; tenorNode; tenorNode = tenorNode->NextSiblingElement("tenor")) { outputTenors.push_back(tuFactory.timeUnit(tenorNode)); } } //compositeCalculation boost::shared_ptr<DefaultProbabilityTermStructure> copulaHazardTS = boost::shared_ptr<DefaultProbabilityTermStructure>(new CopulaHazardRate<ProbabilityOfAtLeastNEvents,Linear>( dates,hazardRateCurves,0.9,1,50)); //result for (Size i=0 ; i<outputNum ; ++i) { dates_.push_back(calendar.advance(refereceDate,outputTenors[i])); dateTimes_.push_back(daycounter.yearFraction(refereceDate,dates_[i])); data_.push_back(copulaHazardTS->defaultProbability(dateTimes_[i])); } // makeXml this->buildResultXml(); //this->buildResultXml(inst_->additionalResults()); } catch (std::exception& e) { std::cerr << e.what() << std::endl; } catch (...) { std::cerr << "unknown error" << std::endl; } }
void DayCounterTest::testThirty360_EurobondBasis() { BOOST_MESSAGE("Testing thirty/360 day counter (Eurobond Basis)..."); // Source: ISDA 2006 Definitions 4.16 (g) // 30E/360 (or Eurobond Basis) // Based on ICMA (Rule 251) and FBF; this is the version of 30E/360 used by Excel DayCounter dayCounter = Thirty360(Thirty360::EurobondBasis); std::vector<Date> testStartDates; std::vector<Date> testEndDates; Time calculated; // ISDA - Example 1: End dates do not involve the last day of February testStartDates.push_back(Date(20, August, 2006)); testEndDates.push_back(Date(20, February, 2007)); testStartDates.push_back(Date(20, February, 2007)); testEndDates.push_back(Date(20, August, 2007)); testStartDates.push_back(Date(20, August, 2007)); testEndDates.push_back(Date(20, February, 2008)); testStartDates.push_back(Date(20, February, 2008)); testEndDates.push_back(Date(20, August, 2008)); testStartDates.push_back(Date(20, August, 2008)); testEndDates.push_back(Date(20, February, 2009)); testStartDates.push_back(Date(20, February, 2009)); testEndDates.push_back(Date(20, August, 2009)); //// ISDA - Example 2: End dates include some end-February dates testStartDates.push_back(Date(28, February, 2006)); testEndDates.push_back(Date(31, August, 2006)); testStartDates.push_back(Date(31, August, 2006)); testEndDates.push_back(Date(28, February, 2007)); testStartDates.push_back(Date(28, February, 2007)); testEndDates.push_back(Date(31, August, 2007)); testStartDates.push_back(Date(31, August, 2007)); testEndDates.push_back(Date(29, February, 2008)); testStartDates.push_back(Date(29, February, 2008)); testEndDates.push_back(Date(31, August, 2008)); testStartDates.push_back(Date(31, August, 2008)); testEndDates.push_back(Date(28, Feb, 2009)); testStartDates.push_back(Date(28, February, 2009)); testEndDates.push_back(Date(31, August, 2009)); testStartDates.push_back(Date(31, August, 2009)); testEndDates.push_back(Date(28, Feb, 2010)); testStartDates.push_back(Date(28, February, 2010)); testEndDates.push_back(Date(31, August, 2010)); testStartDates.push_back(Date(31, August, 2010)); testEndDates.push_back(Date(28, Feb, 2011)); testStartDates.push_back(Date(28, February, 2011)); testEndDates.push_back(Date(31, August, 2011)); testStartDates.push_back(Date(31, August, 2011)); testEndDates.push_back(Date(29, Feb, 2012)); //// ISDA - Example 3: Miscellaneous calculations testStartDates.push_back(Date(31, January, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(30, January, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(28, February, 2006)); testEndDates.push_back(Date(3, March, 2006)); testStartDates.push_back(Date(14, February, 2006)); testEndDates.push_back(Date(28, February, 2006)); testStartDates.push_back(Date(30, September, 2006)); testEndDates.push_back(Date(31, October, 2006)); testStartDates.push_back(Date(31, October, 2006)); testEndDates.push_back(Date(28, November, 2006)); testStartDates.push_back(Date(31, August, 2007)); testEndDates.push_back(Date(28, February, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(28, August, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(30, August, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(31, August, 2008)); testStartDates.push_back(Date(26, February, 2007)); testEndDates.push_back(Date(28, February, 2008)); testStartDates.push_back(Date(26, February, 2007)); testEndDates.push_back(Date(29, February, 2008)); testStartDates.push_back(Date(29, February, 2008)); testEndDates.push_back(Date(28, February, 2009)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(30, March, 2008)); testStartDates.push_back(Date(28, February, 2008)); testEndDates.push_back(Date(31, March, 2008)); int expected[] = { 180, 180, 180, 180, 180, 180, 182, 178, 182, 179, 181, 178, 182, 178, 182, 178, 182, 179, 28, 28, 5, 14, 30, 28, 178, 180, 182, 182, 362, 363, 359, 32, 32 }; for (Size i = 0; i < testStartDates.size(); i++) { calculated = dayCounter.dayCount(testStartDates[i], testEndDates[i]); if (calculated != expected[i]) { BOOST_ERROR("from " << testStartDates[i] << " to " << testEndDates[i] << ":\n" << " calculated: " << calculated << "\n" << " expected: " << expected[i]); } } }
void JamshidianSwaptionEngine::calculate() const { QL_REQUIRE(arguments_.settlementType==Settlement::Physical, "cash-settled swaptions not priced by Jamshidian engine"); QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "cannot use the Jamshidian decomposition " "on exotic swaptions"); QL_REQUIRE(arguments_.swap->spread() == 0.0, "non zero spread (" << arguments_.swap->spread() << ") not allowed"); // PC Date referenceDate; DayCounter dayCounter; boost::shared_ptr<TermStructureConsistentModel> tsmodel = boost::dynamic_pointer_cast<TermStructureConsistentModel>(*model_); if (tsmodel) { referenceDate = tsmodel->termStructure()->referenceDate(); dayCounter = tsmodel->termStructure()->dayCounter(); } else { referenceDate = termStructure_->referenceDate(); dayCounter = termStructure_->dayCounter(); } std::vector<Real> amounts(arguments_.fixedCoupons); amounts.back() += arguments_.nominal; Real maturity = dayCounter.yearFraction(referenceDate, arguments_.exercise->date(0)); std::vector<Time> fixedPayTimes(arguments_.fixedPayDates.size()); Time valueTime = dayCounter.yearFraction(referenceDate,arguments_.fixedResetDates[0]); for (Size i=0; i<fixedPayTimes.size(); i++) fixedPayTimes[i] = dayCounter.yearFraction(referenceDate, arguments_.fixedPayDates[i]); rStarFinder finder(*model_, arguments_.nominal, maturity, valueTime, fixedPayTimes, amounts); Brent s1d; Rate minStrike = -10.0; Rate maxStrike = 10.0; s1d.setMaxEvaluations(10000); s1d.setLowerBound(minStrike); s1d.setUpperBound(maxStrike); Rate rStar = s1d.solve(finder, 1e-8, 0.05, minStrike, maxStrike); Option::Type w = arguments_.type==VanillaSwap::Payer ? Option::Put : Option::Call; Size size = arguments_.fixedCoupons.size(); Real value = 0.0; for (Size i=0; i<size; i++) { Real fixedPayTime = dayCounter.yearFraction(referenceDate, arguments_.fixedPayDates[i]); Real strike = model_->discountBond(maturity, fixedPayTime, rStar) / model_->discountBond(maturity,valueTime,rStar); Real dboValue = model_->discountBondOption( w, strike, maturity, valueTime, fixedPayTime); value += amounts[i]*dboValue; } results_.value = value; }
void CreditDefaultSwapTest::testCachedMarketValue() { BOOST_TEST_MESSAGE( "Testing credit-default swap against cached market values..."); SavedSettings backup; Settings::instance().evaluationDate() = Date(9,June,2006); Date evalDate = Settings::instance().evaluationDate(); Calendar calendar = UnitedStates(); std::vector<Date> discountDates; discountDates.push_back(evalDate); discountDates.push_back(calendar.advance(evalDate, 1, Weeks, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 1, Months, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 2, Months, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 3, Months, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 6, Months, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate,12, Months, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 2, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 3, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 4, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 5, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 6, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 7, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 8, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate, 9, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate,10, Years, ModifiedFollowing)); discountDates.push_back(calendar.advance(evalDate,15, Years, ModifiedFollowing)); std::vector<DiscountFactor> dfs; dfs.push_back(1.0); dfs.push_back(0.9990151375768731); dfs.push_back(0.99570502636871183); dfs.push_back(0.99118260474528685); dfs.push_back(0.98661167950906203); dfs.push_back(0.9732592953359388 ); dfs.push_back(0.94724424481038083); dfs.push_back(0.89844996737120875 ); dfs.push_back(0.85216647839921411 ); dfs.push_back(0.80775477692556874 ); dfs.push_back(0.76517289234200347 ); dfs.push_back(0.72401019553182933 ); dfs.push_back(0.68503909569219212 ); dfs.push_back(0.64797499814013748 ); dfs.push_back(0.61263171936255534 ); dfs.push_back(0.5791942350748791 ); dfs.push_back(0.43518868769953606 ); const DayCounter& curveDayCounter=Actual360(); RelinkableHandle<YieldTermStructure> discountCurve; discountCurve.linkTo( ext::shared_ptr<YieldTermStructure>( new DiscountCurve(discountDates, dfs, curveDayCounter))); DayCounter dayCounter = Thirty360(); std::vector<Date> dates; dates.push_back(evalDate); dates.push_back(calendar.advance(evalDate, 6, Months, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 1, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 2, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 3, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 4, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 5, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate, 7, Years, ModifiedFollowing)); dates.push_back(calendar.advance(evalDate,10, Years, ModifiedFollowing)); std::vector<Probability> defaultProbabilities; defaultProbabilities.push_back(0.0000); defaultProbabilities.push_back(0.0047); defaultProbabilities.push_back(0.0093); defaultProbabilities.push_back(0.0286); defaultProbabilities.push_back(0.0619); defaultProbabilities.push_back(0.0953); defaultProbabilities.push_back(0.1508); defaultProbabilities.push_back(0.2288); defaultProbabilities.push_back(0.3666); std::vector<Real> hazardRates; hazardRates.push_back(0.0); for (Size i=1; i<dates.size(); ++i) { Time t1 = dayCounter.yearFraction(dates[0], dates[i-1]); Time t2 = dayCounter.yearFraction(dates[0], dates[i]); Probability S1 = 1.0 - defaultProbabilities[i-1]; Probability S2 = 1.0 - defaultProbabilities[i]; hazardRates.push_back(std::log(S1/S2)/(t2-t1)); } RelinkableHandle<DefaultProbabilityTermStructure> piecewiseFlatHazardRate; piecewiseFlatHazardRate.linkTo( ext::shared_ptr<DefaultProbabilityTermStructure>( new InterpolatedHazardRateCurve<BackwardFlat>(dates, hazardRates, Thirty360()))); // Testing credit default swap // Build the schedule Date issueDate(20, March, 2006); Date maturity(20, June, 2013); Frequency cdsFrequency = Semiannual; BusinessDayConvention cdsConvention = ModifiedFollowing; Schedule schedule(issueDate, maturity, Period(cdsFrequency), calendar, cdsConvention, cdsConvention, DateGeneration::Forward, false); // Build the CDS Real recoveryRate = 0.25; Rate fixedRate=0.0224; DayCounter dayCount=Actual360(); Real cdsNotional=100.0; CreditDefaultSwap cds(Protection::Seller, cdsNotional, fixedRate, schedule, cdsConvention, dayCount, true, true); cds.setPricingEngine(ext::shared_ptr<PricingEngine>( new MidPointCdsEngine(piecewiseFlatHazardRate, recoveryRate,discountCurve))); Real calculatedNpv = cds.NPV(); Real calculatedFairRate = cds.fairSpread(); double npv = -1.364048777; // from Bloomberg we have 98.15598868 - 100.00; double fairRate = 0.0248429452; // from Bloomberg we have 0.0258378; Real tolerance = 1e-9; if (std::fabs(npv - calculatedNpv) > tolerance) BOOST_ERROR( "Failed to reproduce the npv for the given credit-default swap\n" << std::setprecision(10) << " computed NPV: " << calculatedNpv << "\n" << " Given NPV: " << npv); if (std::fabs(fairRate - calculatedFairRate) > tolerance) BOOST_ERROR( "Failed to reproduce the fair rate for the given credit-default swap\n" << std::setprecision(10) << " computed fair rate: " << calculatedFairRate << "\n" << " Given fair rate: " << fairRate); }
void BinomialBarrierEngine<T,D>::calculate() const { DayCounter rfdc = process_->riskFreeRate()->dayCounter(); DayCounter divdc = process_->dividendYield()->dayCounter(); DayCounter voldc = process_->blackVolatility()->dayCounter(); Calendar volcal = process_->blackVolatility()->calendar(); Real s0 = process_->stateVariable()->value(); QL_REQUIRE(s0 > 0.0, "negative or null underlying given"); Volatility v = process_->blackVolatility()->blackVol( arguments_.exercise->lastDate(), s0); Date maturityDate = arguments_.exercise->lastDate(); Rate r = process_->riskFreeRate()->zeroRate(maturityDate, rfdc, Continuous, NoFrequency); Rate q = process_->dividendYield()->zeroRate(maturityDate, divdc, Continuous, NoFrequency); Date referenceDate = process_->riskFreeRate()->referenceDate(); // binomial trees with constant coefficient Handle<YieldTermStructure> flatRiskFree( boost::shared_ptr<YieldTermStructure>( new FlatForward(referenceDate, r, rfdc))); Handle<YieldTermStructure> flatDividends( boost::shared_ptr<YieldTermStructure>( new FlatForward(referenceDate, q, divdc))); Handle<BlackVolTermStructure> flatVol( boost::shared_ptr<BlackVolTermStructure>( new BlackConstantVol(referenceDate, volcal, v, voldc))); boost::shared_ptr<StrikedTypePayoff> payoff = boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff); QL_REQUIRE(payoff, "non-striked payoff given"); Time maturity = rfdc.yearFraction(referenceDate, maturityDate); boost::shared_ptr<StochasticProcess1D> bs( new GeneralizedBlackScholesProcess( process_->stateVariable(), flatDividends, flatRiskFree, flatVol)); // correct timesteps to ensure a (local) minimum, using Boyle and Lau // approach. See Journal of Derivatives, 1/1994, // "Bumping up against the barrier with the binomial method" // Note: this approach works only for CoxRossRubinstein lattices, so // is disabled if T is not a CoxRossRubinstein or derived from it. Size optimum_steps = timeSteps_; if (boost::is_base_of<CoxRossRubinstein, T>::value && maxTimeSteps_ > timeSteps_ && s0 > 0 && arguments_.barrier > 0) { Real divisor; if (s0 > arguments_.barrier) divisor = std::pow(std::log(s0 / arguments_.barrier), 2); else divisor = std::pow(std::log(arguments_.barrier / s0), 2); if (!close(divisor,0)) { for (Size i=1; i < timeSteps_ ; ++i) { Size optimum = Size(( i*i * v*v * maturity) / divisor); if (timeSteps_ < optimum) { optimum_steps = optimum; break; // found first minimum with iterations>=timesteps } } } if (optimum_steps > maxTimeSteps_) optimum_steps = maxTimeSteps_; // too high, limit } TimeGrid grid(maturity, optimum_steps); boost::shared_ptr<T> tree(new T(bs, maturity, optimum_steps, payoff->strike())); boost::shared_ptr<BlackScholesLattice<T> > lattice( new BlackScholesLattice<T>(tree, r, maturity, optimum_steps)); D option(arguments_, *process_, grid); option.initialize(lattice, maturity); // Partial derivatives calculated from various points in the // binomial tree // (see J.C.Hull, "Options, Futures and other derivatives", 6th edition, pp 397/398) // Rollback to third-last step, and get underlying prices (s2) & // option values (p2) at this point option.rollback(grid[2]); Array va2(option.values()); QL_ENSURE(va2.size() == 3, "Expect 3 nodes in grid at second step"); Real p2u = va2[2]; // up Real p2m = va2[1]; // mid Real p2d = va2[0]; // down (low) Real s2u = lattice->underlying(2, 2); // up price Real s2m = lattice->underlying(2, 1); // middle price Real s2d = lattice->underlying(2, 0); // down (low) price // calculate gamma by taking the first derivate of the two deltas Real delta2u = (p2u - p2m)/(s2u-s2m); Real delta2d = (p2m-p2d)/(s2m-s2d); Real gamma = (delta2u - delta2d) / ((s2u-s2d)/2); // Rollback to second-last step, and get option values (p1) at // this point option.rollback(grid[1]); Array va(option.values()); QL_ENSURE(va.size() == 2, "Expect 2 nodes in grid at first step"); Real p1u = va[1]; Real p1d = va[0]; Real s1u = lattice->underlying(1, 1); // up (high) price Real s1d = lattice->underlying(1, 0); // down (low) price Real delta = (p1u - p1d) / (s1u - s1d); // Finally, rollback to t=0 option.rollback(0.0); Real p0 = option.presentValue(); // Store results results_.value = p0; results_.delta = delta; results_.gamma = gamma; // theta can be approximated by calculating the numerical derivative // between mid value at third-last step and at t0. The underlying price // is the same, only time varies. results_.theta = (p2m - p0) / grid[2]; }