void SwingOptionTest::testExtOUJumpVanillaEngine() { BOOST_TEST_MESSAGE("Testing finite difference pricer for the Kluge model..."); SavedSettings backup; boost::shared_ptr<ExtOUWithJumpsProcess> jumpProcess = createKlugeProcess(); const Date today = Date::todaysDate(); Settings::instance().evaluationDate() = today; const DayCounter dc = ActualActual(); const Date maturityDate = today + Period(12, Months); const Time maturity = dc.yearFraction(today, maturityDate); const Rate irRate = 0.1; boost::shared_ptr<YieldTermStructure> rTS(flatRate(today, irRate, dc)); boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(Option::Call, 30)); boost::shared_ptr<Exercise> exercise(new EuropeanExercise(maturityDate)); boost::shared_ptr<PricingEngine> engine( new FdExtOUJumpVanillaEngine(jumpProcess, rTS, 25, 200, 50)); VanillaOption option(payoff, exercise); option.setPricingEngine(engine); const Real fdNPV = option.NPV(); const Size steps = 100; const Size nrTrails = 200000; TimeGrid grid(maturity, steps); 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)); GeneralStatistics npv; MultiPathGenerator<rsg_type> generator(jumpProcess, grid, rsg, false); for (Size n=0; n < nrTrails; ++n) { sample_type path = generator.next(); const Real x = path.value[0].back(); const Real y = path.value[1].back(); const Real cashflow = (*payoff)(std::exp(x+y)); npv.add(cashflow*rTS->discount(maturity)); } const Real mcNPV = npv.mean(); const Real mcError = npv.errorEstimate(); if ( std::fabs(fdNPV - mcNPV) > 3.0*mcError) { BOOST_ERROR("Failed to reproduce FD and MC prices" << "\n FD NPV: " << fdNPV << "\n MC NPV: " << mcNPV << " +/- " << mcError); } }
void LiborMarketModelTest::testSwaptionPricing() { BOOST_TEST_MESSAGE("Testing forward swap and swaption pricing..."); SavedSettings backup; const Size size = 10; const Size steps = 8*size; #if defined(QL_USE_INDEXED_COUPON) const Real tolerance = 1e-6; #else const Real tolerance = 1e-12; #endif std::vector<Date> dates; std::vector<Rate> rates; dates.push_back(Date(4,September,2005)); dates.push_back(Date(4,September,2011)); rates.push_back(0.04); rates.push_back(0.08); boost::shared_ptr<IborIndex> index = makeIndex(dates, rates); boost::shared_ptr<LiborForwardModelProcess> process( new LiborForwardModelProcess(size, index)); boost::shared_ptr<LmCorrelationModel> corrModel( new LmExponentialCorrelationModel(size, 0.5)); boost::shared_ptr<LmVolatilityModel> volaModel( new LmLinearExponentialVolatilityModel(process->fixingTimes(), 0.291, 1.483, 0.116, 0.00001)); // set-up pricing engine process->setCovarParam(boost::shared_ptr<LfmCovarianceParameterization>( new LfmCovarianceProxy(volaModel, corrModel))); // set-up a small Monte-Carlo simulation to price swations typedef PseudoRandom::rsg_type rsg_type; typedef MultiPathGenerator<rsg_type>::sample_type sample_type; std::vector<Time> tmp = process->fixingTimes(); TimeGrid grid(tmp.begin(), tmp.end(), steps); Size i; std::vector<Size> location; for (i=0; i < tmp.size(); ++i) { location.push_back( std::find(grid.begin(),grid.end(),tmp[i])-grid.begin()); } rsg_type rsg = PseudoRandom::make_sequence_generator( process->factors()*(grid.size()-1), BigNatural(42)); const Size nrTrails = 5000; MultiPathGenerator<rsg_type> generator(process, grid, rsg, false); boost::shared_ptr<LiborForwardModel> liborModel(new LiborForwardModel(process, volaModel, corrModel)); Calendar calendar = index->fixingCalendar(); DayCounter dayCounter = index->forwardingTermStructure()->dayCounter(); BusinessDayConvention convention = index->businessDayConvention(); Date settlement = index->forwardingTermStructure()->referenceDate(); boost::shared_ptr<SwaptionVolatilityMatrix> m = liborModel->getSwaptionVolatilityMatrix(); for (i=1; i < size; ++i) { for (Size j=1; j <= size-i; ++j) { Date fwdStart = settlement + Period(6*i, Months); Date fwdMaturity = fwdStart + Period(6*j, Months); Schedule schedule(fwdStart, fwdMaturity, index->tenor(), calendar, convention, convention, DateGeneration::Forward, false); Rate swapRate = 0.0404; boost::shared_ptr<VanillaSwap> forwardSwap( new VanillaSwap(VanillaSwap::Receiver, 1.0, schedule, swapRate, dayCounter, schedule, index, 0.0, index->dayCounter())); forwardSwap->setPricingEngine(boost::shared_ptr<PricingEngine>( new DiscountingSwapEngine(index->forwardingTermStructure()))); // check forward pricing first const Real expected = forwardSwap->fairRate(); const Real calculated = liborModel->S_0(i-1,i+j-1); if (std::fabs(expected - calculated) > tolerance) BOOST_ERROR("Failed to reproduce fair forward swap rate" << "\n calculated: " << calculated << "\n expected: " << expected); swapRate = forwardSwap->fairRate(); forwardSwap = boost::shared_ptr<VanillaSwap>( new VanillaSwap(VanillaSwap::Receiver, 1.0, schedule, swapRate, dayCounter, schedule, index, 0.0, index->dayCounter())); forwardSwap->setPricingEngine(boost::shared_ptr<PricingEngine>( new DiscountingSwapEngine(index->forwardingTermStructure()))); if (i == j && i<=size/2) { boost::shared_ptr<PricingEngine> engine( new LfmSwaptionEngine(liborModel, index->forwardingTermStructure())); boost::shared_ptr<Exercise> exercise( new EuropeanExercise(process->fixingDates()[i])); boost::shared_ptr<Swaption> swaption( new Swaption(forwardSwap, exercise)); swaption->setPricingEngine(engine); GeneralStatistics stat; for (Size n=0; n<nrTrails; ++n) { sample_type path = (n%2) ? generator.antithetic() : generator.next(); std::vector<Rate> rates(size); for (Size k=0; k<process->size(); ++k) { rates[k] = path.value[k][location[i]]; } std::vector<DiscountFactor> dis = process->discountBond(rates); Real npv=0.0; for (Size m=i; m < i+j; ++m) { npv += (swapRate - rates[m]) * ( process->accrualEndTimes()[m] - process->accrualStartTimes()[m])*dis[m]; } stat.add(std::max(npv, 0.0)); } if (std::fabs(swaption->NPV() - stat.mean()) > stat.errorEstimate()*2.35) BOOST_ERROR("Failed to reproduce swaption npv" << "\n calculated: " << stat.mean() << "\n expected: " << swaption->NPV()); } } } }
void VPPTest::testVPPPricing() { BOOST_TEST_MESSAGE("Testing VPP pricing using perfect foresight or FDM..."); SavedSettings backup; const Date today = Date(18, December, 2011); const DayCounter dc = ActualActual(); Settings::instance().evaluationDate() = today; // vpp paramter const Real heatRate = 2.5; const Real pMin = 8; const Real pMax = 40; const Size tMinUp = 6; const Size tMinDown = 2; const Real startUpFuel = 20; const Real startUpFixCost = 100; const boost::shared_ptr<SwingExercise> exercise( new SwingExercise(today, today+6, 3600u)); VanillaVPPOption vppOption(heatRate, pMin, pMax, tMinUp, tMinDown, startUpFuel, startUpFixCost, exercise); const boost::shared_ptr<KlugeExtOUProcess> klugeOUProcess = createKlugeExtOUProcess(); const boost::shared_ptr<ExtOUWithJumpsProcess> lnPowerProcess = klugeOUProcess->getKlugeProcess(); const boost::shared_ptr<ExtendedOrnsteinUhlenbeckProcess> ouProcess = lnPowerProcess->getExtendedOrnsteinUhlenbeckProcess(); const boost::shared_ptr<ExtendedOrnsteinUhlenbeckProcess> lnGasProcess = klugeOUProcess->getExtOUProcess(); const Real beta = lnPowerProcess->beta(); const Real eta = lnPowerProcess->eta(); const Real lambda = lnPowerProcess->jumpIntensity(); const Real alpha = ouProcess->speed(); const Real volatility_x = ouProcess->volatility(); const Real kappa = lnGasProcess->speed(); const Real volatility_u = lnGasProcess->volatility(); const Rate irRate = 0.00; const Real fuelCostAddon = 3.0; const boost::shared_ptr<YieldTermStructure> rTS = flatRate(today, irRate, dc); const Size nHours = LENGTH(powerPrices); typedef FdSimpleKlugeExtOUVPPEngine::Shape Shape; boost::shared_ptr<Shape> fuelShape(new Shape(nHours)); boost::shared_ptr<Shape> powerShape(new Shape(nHours)); for (Size i=0; i < nHours; ++i) { const Time t = (i+1)/(365*24.); const Real fuelPrice = fuelPrices[i]; const Real gs = std::log(fuelPrice)-square<Real>()(volatility_u) /(4*kappa)*(1-std::exp(-2*kappa*t)); (*fuelShape)[i] = Shape::value_type(t, gs); const Real powerPrice = powerPrices[i]; const Real ps = std::log(powerPrice)-square<Real>()(volatility_x) /(4*alpha)*(1-std::exp(-2*alpha*t)) -lambda/beta*std::log((eta-std::exp(-beta*t))/(eta-1.0)); (*powerShape)[i] = Shape::value_type(t, ps); } // Test: intrinsic value vppOption.setPricingEngine(boost::shared_ptr<PricingEngine>( new DynProgVPPIntrinsicValueEngine( std::vector<Real>(fuelPrices, fuelPrices+nHours), std::vector<Real>(powerPrices, powerPrices+nHours), fuelCostAddon, flatRate(0.0, dc)))); const Real intrinsic = vppOption.NPV(); const Real expectedIntrinsic = 2056.04; if (std::fabs(intrinsic - expectedIntrinsic) > 0.1) { BOOST_ERROR("Failed to reproduce intrinsic value" << "\n calculated: " << intrinsic << "\n expected : " << expectedIntrinsic); } // Test: finite difference price const boost::shared_ptr<PricingEngine> engine( new FdSimpleKlugeExtOUVPPEngine(klugeOUProcess, rTS, fuelShape, powerShape, fuelCostAddon, 1, 25, 11, 10)); vppOption.setPricingEngine(engine); const Real fdmPrice = vppOption.NPV(); const Real expectedFdmPrice = 5217.68; if (std::fabs(fdmPrice - expectedFdmPrice) > 0.1) { BOOST_ERROR("Failed to reproduce finite difference price" << "\n calculated: " << fdmPrice << "\n expected : " << expectedFdmPrice); } // Test: Monte-Carlo perfect foresight price VanillaVPPOption::arguments args; vppOption.setupArguments(&args); const FdmVPPStepConditionFactory stepConditionFactory(args); const boost::shared_ptr<FdmMesher> oneDimMesher(new FdmMesherComposite( stepConditionFactory.stateMesher())); const Size nStates = oneDimMesher->layout()->dim()[0]; const FdmVPPStepConditionMesher vppMesh = { 0u, oneDimMesher }; const TimeGrid grid(dc.yearFraction(today, exercise->lastDate()+1), exercise->dates().size()); typedef PseudoRandom::rsg_type rsg_type; typedef MultiPathGenerator<rsg_type>::sample_type sample_type; rsg_type rsg = PseudoRandom::make_sequence_generator( klugeOUProcess->factors()*(grid.size()-1), 1234ul); MultiPathGenerator<rsg_type> generator(klugeOUProcess, grid, rsg, false); GeneralStatistics npv; const Size nTrails = 2500; for (Size i=0; i < nTrails; ++i) { const sample_type& path = generator.next(); const boost::shared_ptr<FdmVPPStepCondition> stepCondition( stepConditionFactory.build( vppMesh, fuelCostAddon, boost::shared_ptr<FdmInnerValueCalculator>( new PathFuelPrice(path.value, fuelShape)), boost::shared_ptr<FdmInnerValueCalculator>( new PathSparkSpreadPrice(heatRate, path.value, fuelShape, powerShape)))); Array state(nStates, 0.0); for (Size j=exercise->dates().size(); j > 0; --j) { stepCondition->applyTo(state, grid.at(j)); state*=rTS->discount(grid.at(j))/rTS->discount(grid.at(j-1)); } npv.add(state.back()); } Real npvMC = npv.mean(); Real errorMC = npv.errorEstimate(); const Real expectedMC = 5250.0; if (std::fabs(npvMC-expectedMC) > 3*errorMC) { BOOST_ERROR("Failed to reproduce Monte-Carlo price" << "\n calculated: " << npvMC << "\n error ; " << errorMC << "\n expected : " << expectedMC); } npv.reset(); // Test: Longstaff Schwartz least square Monte-Carlo const Size nCalibrationTrails = 1000u; std::vector<sample_type> calibrationPaths; std::vector<boost::shared_ptr<FdmVPPStepCondition> > stepConditions; std::vector<boost::shared_ptr<FdmInnerValueCalculator> > sparkSpreads; sparkSpreads.reserve(nCalibrationTrails); stepConditions.reserve(nCalibrationTrails); calibrationPaths.reserve(nCalibrationTrails); for (Size i=0; i < nCalibrationTrails; ++i) { calibrationPaths.push_back(generator.next()); sparkSpreads.push_back(boost::shared_ptr<FdmInnerValueCalculator>( new PathSparkSpreadPrice(heatRate, calibrationPaths.back().value, fuelShape, powerShape))); stepConditions.push_back(stepConditionFactory.build( vppMesh, fuelCostAddon, boost::shared_ptr<FdmInnerValueCalculator>( new PathFuelPrice(calibrationPaths.back().value, fuelShape)), sparkSpreads.back())); } const FdmLinearOpIterator iter = oneDimMesher->layout()->begin(); // prices of all calibration paths for all states std::vector<Array> prices(nCalibrationTrails, Array(nStates, 0.0)); // regression coefficients for all states and all exercise dates std::vector<std::vector<Array> > coeff( nStates, std::vector<Array>(exercise->dates().size(), Array())); // regression functions const Size dim = 1u; std::vector<boost::function1<Real, Array> > v( LsmBasisSystem::multiPathBasisSystem(dim,5u, LsmBasisSystem::Monomial)); for (Size i=exercise->dates().size(); i > 0u; --i) { const Time t = grid.at(i); std::vector<Array> x(nCalibrationTrails, Array(dim)); for (Size j=0; j < nCalibrationTrails; ++j) { x[j][0] = sparkSpreads[j]->innerValue(iter, t); } for (Size k=0; k < nStates; ++k) { std::vector<Real> y(nCalibrationTrails); for (Size j=0; j < nCalibrationTrails; ++j) { y[j] = prices[j][k]; } coeff[k][i-1] = GeneralLinearLeastSquares(x, y, v).coefficients(); for (Size j=0; j < nCalibrationTrails; ++j) { prices[j][k] = 0.0; for (Size l=0; l < v.size(); ++l) { prices[j][k] += coeff[k][i-1][l]*v[l](x[j]); } } } for (Size j=0; j < nCalibrationTrails; ++j) { stepConditions[j]->applyTo(prices[j], grid.at(i)); } } Real tmpValue = 0.0; for (Size i=0; i < nTrails; ++i) { Array x(dim), state(nStates, 0.0), contState(nStates, 0.0); const sample_type& path = (i % 2) ? generator.antithetic() : generator.next(); const boost::shared_ptr<FdmInnerValueCalculator> fuelPrices( new PathFuelPrice(path.value, fuelShape)); const boost::shared_ptr<FdmInnerValueCalculator> sparkSpreads( new PathSparkSpreadPrice(heatRate, path.value, fuelShape, powerShape)); for (Size j=exercise->dates().size(); j > 0u; --j) { const Time t = grid.at(j); const Real fuelPrice = fuelPrices->innerValue(iter, t); const Real sparkSpread = sparkSpreads->innerValue(iter, t); const Real startUpCost = startUpFixCost + (fuelPrice + fuelCostAddon)*startUpFuel; x[0] = sparkSpread; for (Size k=0; k < nStates; ++k) { contState[k] = 0.0; for (Size l=0; l < v.size(); ++l) { contState[k] += coeff[k][j-1][l]*v[l](x); } } const Real pMinFlow = pMin*(sparkSpread - heatRate*fuelCostAddon); const Real pMaxFlow = pMax*(sparkSpread - heatRate*fuelCostAddon); // rollback continuation states and the path states for (Size i=0; i < 2*tMinUp; ++i) { if (i < tMinUp) { state[i] += pMinFlow; contState[i]+= pMinFlow; } else { state[i] += pMaxFlow; contState[i]+= pMaxFlow; } } // dynamic programming using the continuation values Array retVal(nStates); for (Size i=0; i < tMinUp-1; ++i) { retVal[i] = retVal[tMinUp + i] = (contState[i+1] > contState[tMinUp + i+1])? state[i+1] : state[tMinUp + i+1]; } if (contState[2*tMinUp] >= std::max(contState[tMinUp-1], contState[2*tMinUp-1])) { retVal[tMinUp-1] = retVal[2*tMinUp-1] = state[2*tMinUp]; } else if (contState[tMinUp-1] >= contState[2*tMinUp-1]) { retVal[tMinUp-1] = retVal[2*tMinUp-1] = state[tMinUp-1]; } else { retVal[tMinUp-1] = retVal[2*tMinUp-1] = state[2*tMinUp-1]; } for (Size i=0; i < tMinDown-1; ++i) { retVal[2*tMinUp + i] = state[2*tMinUp + i+1]; } if (contState.back() >= std::max(contState.front(), contState[tMinUp]) - startUpCost) { retVal.back() = state.back(); } else if (contState.front() > contState[tMinUp]) { retVal.back() = state.front()-startUpCost; } else { retVal.back() = state[tMinUp]-startUpCost; } state = retVal; } tmpValue+=0.5*state.back(); if ((i%2)) { npv.add(tmpValue, 1.0); tmpValue = 0.0; } } npvMC = npv.mean(); errorMC = npv.errorEstimate(); if (std::fabs(npvMC-fdmPrice) > 3*errorMC) { BOOST_ERROR("Failed to reproduce Least Square Monte-Carlo price" << "\n calculated : " << npvMC << "\n error : " << errorMC << "\n expected FDM : " << fdmPrice); } }
void VPPTest::testKlugeExtOUSpreadOption() { BOOST_TEST_MESSAGE("Testing Simple Kluge ext-Ornstein-Uhlenbeck spread option..."); SavedSettings backup; Date settlementDate = Date(18, December, 2011); Settings::instance().evaluationDate() = settlementDate; DayCounter dayCounter = ActualActual(); Date maturityDate = settlementDate + Period(1, Years); Time maturity = dayCounter.yearFraction(settlementDate, maturityDate); const Real speed = 1.0; const Volatility vol = std::sqrt(1.4); const Real betaG = 0.0; const Real alphaG = 3.0; const Real x0G = 3.0; const Rate irRate = 0.0; const Real heatRate = 2.0; const Real rho = 0.5; boost::shared_ptr<ExtOUWithJumpsProcess> klugeProcess = createKlugeProcess(); boost::function<Real (Real)> f = alphaG + betaG*boost::lambda::_1; boost::shared_ptr<ExtendedOrnsteinUhlenbeckProcess> extOUProcess( new ExtendedOrnsteinUhlenbeckProcess(speed, vol, x0G, f, ExtendedOrnsteinUhlenbeckProcess::Trapezodial)); boost::shared_ptr<YieldTermStructure> rTS( flatRate(settlementDate, irRate, dayCounter)); boost::shared_ptr<KlugeExtOUProcess> klugeOUProcess( new KlugeExtOUProcess(rho, klugeProcess, extOUProcess)); boost::shared_ptr<Payoff> payoff(new PlainVanillaPayoff(Option::Call, 0.0)); Array spreadFactors(2); spreadFactors[0] = 1.0; spreadFactors[1] = -heatRate; boost::shared_ptr<BasketPayoff> basketPayoff( new AverageBasketPayoff(payoff, spreadFactors)); boost::shared_ptr<Exercise> exercise(new EuropeanExercise(maturityDate)); BasketOption option(basketPayoff, exercise); option.setPricingEngine(boost::shared_ptr<PricingEngine>( new FdKlugeExtOUSpreadEngine(klugeOUProcess, rTS, 5, 200, 50, 20))); TimeGrid grid(maturity, 50); typedef PseudoRandom::rsg_type rsg_type; typedef MultiPathGenerator<rsg_type>::sample_type sample_type; rsg_type rsg = PseudoRandom::make_sequence_generator( klugeOUProcess->factors()*(grid.size()-1), 1234ul); MultiPathGenerator<rsg_type> generator(klugeOUProcess, grid, rsg, false); GeneralStatistics npv; const Size nTrails = 20000; for (Size i=0; i < nTrails; ++i) { const sample_type path = generator.next(); Array p(2); p[0] = path.value[0].back() + path.value[1].back(); p[1] = path.value[2].back(); npv.add((*basketPayoff)(Exp(p))); } const Real calculated = option.NPV(); const Real expectedMC = npv.mean(); const Real mcError = npv.errorEstimate(); if (std::fabs(expectedMC - calculated) > 3*mcError) { BOOST_ERROR("Failed to reproduce referenc values" << "\n calculated: " << calculated << "\n expected(MC): " << expectedMC << "\n mc error : " << mcError); } }
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); } } }