A minor rework of the Equity Option example in QuantLib
To enable some experiments with QuantLib I've reorganised the EquityOption example so that each of the pricing methods sits in a separate function. It is a pretty trivial change but I think it makes the example a bit more readable. Feel free to use in accordance with the QuantLib licence.
The performance and functionality of the example should be unaffected.
The code
Here is the code:
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /*! Copyright (C) 2005, 2006, 2007 StatPro Italia srl Copyright (C) 2008 Bojan Nikolic This file is part of QuantLib, a free-software/open-source library for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email <quantlib-dev@lists.sf.net>. The license is also available online at <http://quantlib.org/license.shtml>. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. */ // the only header you need to use QuantLib #include <ql/quantlib.hpp> #ifdef BOOST_MSVC /* Uncomment the following lines to unmask floating-point exceptions. Warning: unpredictable results can arise... See http://www.wilmott.com/messageview.cfm?catid=10&threadid=9481 Is there anyone with a definitive word about this? */ // #include <float.h> // namespace { unsigned int u = _controlfp(_EM_INEXACT, _MCW_EM); } #endif #include <boost/timer.hpp> #include <boost/variant.hpp> #include <iostream> #include <iomanip> using namespace QuantLib; #if defined(QL_ENABLE_SESSIONS) namespace QuantLib { Integer sessionId() { return 0; } } #endif struct OptionInputs { Option::Type type; Real underlying; Real strike; Spread dividendYield; Rate riskFreeRate; Volatility volatility; Date maturity; DayCounter dayCounter; }; void PrintInputs(std::ostream & os, const OptionInputs &in) { os << "Option type = " << in.type << std::endl; os << "Maturity = " << in.maturity << std::endl; os << "Underlying price = " << in.underlying << std::endl; os << "Strike = " << in.strike << std::endl; os << "Risk-free interest rate = " << io::rate(in.riskFreeRate) << std::endl; os << "Dividend yield = " << io::rate(in.dividendYield) << std::endl; os << "Volatility = " << io::volatility(in.volatility) << std::endl; os << std::endl; } typedef boost::variant<const std::string &, double, const char * > OutputEl; /** A simple helper class to print the output variant */ class OutputElPrinter: public boost::static_visitor<> { std::ostream & os; public: OutputElPrinter (std::ostream & os) : os(os) { } void operator()(const std::string & str) const { os << str; } void operator()(const char * str) const { os << str; } void operator()(double d) const { os << d; } }; void PrintResRow( const std::string & method, OutputEl Euro, OutputEl Berm, OutputEl Amer) { Size widths[] = { 35, 14, 14, 14 }; OutputElPrinter op(std::cout); std::cout << std::setw(widths[0]) << std::left << method << std::setw(widths[1]) << std::left; boost::apply_visitor(op,Euro); std::cout << std::setw(widths[2]) << std::left; boost::apply_visitor(op,Berm); std::cout << std::setw(widths[3]) << std::left; boost::apply_visitor(op,Amer); std::cout << std::endl; } void BlackScholesEx( VanillaOption & euro, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { boost::shared_ptr<PricingEngine> pe ( new AnalyticEuropeanEngine(bsmProcess)) ; euro.setPricingEngine(pe); PrintResRow("Black-Scholes", euro.NPV(), "N/A", "N/A"); } void BaroneAdesiWhaleyEx( VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { boost::shared_ptr<PricingEngine> pe ( new BaroneAdesiWhaleyApproximationEngine(bsmProcess) ); amer.setPricingEngine(pe); PrintResRow("Barone-Adesi/Whaley", "N/A", "N/A", amer.NPV()); } void BjerksundStenslandEx( VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { boost::shared_ptr<PricingEngine> pe ( new BjerksundStenslandApproximationEngine(bsmProcess) ); amer.setPricingEngine(pe); PrintResRow("Bjerksund/Stensland", "N/A", "N/A", amer.NPV()); } void IntegralEx( VanillaOption & euro, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { boost::shared_ptr<PricingEngine> pe(new IntegralEngine(bsmProcess)); euro.setPricingEngine(pe); PrintResRow("Integral", euro.NPV(), "N/A", "N/A"); } void FiniteDifferencesEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new FDEuropeanEngine(bsmProcess, timeSteps, timeSteps-1)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new FDBermudanEngine(bsmProcess, timeSteps, timeSteps-1)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new FDAmericanEngine(bsmProcess, timeSteps, timeSteps-1)); amer.setPricingEngine(amer_pe); PrintResRow("Finite differences", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialJarrowRuddEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Jarrow-Rudd", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialCoxRossRubinstein( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Cox-Ross-Rubinstein", euro.NPV(), berm.NPV(), amer.NPV()); } void AdditiveEquiprobabilitiesEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Additive equiprobabilities", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialTrigeorgisEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Trigeorgis", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialTianEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Tian", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialLeisenReimerEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Leisen-Reimer", euro.NPV(), berm.NPV(), amer.NPV()); } void BinomialJoshiEx( VanillaOption & euro, VanillaOption & berm, VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess, Size timeSteps) { boost::shared_ptr<PricingEngine> euro_pe(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)); euro.setPricingEngine(euro_pe); boost::shared_ptr<PricingEngine> berm_pe(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)); berm.setPricingEngine(berm_pe); boost::shared_ptr<PricingEngine> amer_pe(new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)); amer.setPricingEngine(amer_pe); PrintResRow("Binomial Joshi", euro.NPV(), berm.NPV(), amer.NPV()); } void MCCrudeEx( VanillaOption & euro, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { Size timeSteps = 1; Size mcSeed = 42; boost::shared_ptr<PricingEngine> mcengine1; mcengine1 = MakeMCEuropeanEngine<PseudoRandom>(bsmProcess) .withSteps(timeSteps) .withTolerance(0.02) .withSeed(mcSeed); euro.setPricingEngine(mcengine1); PrintResRow("MC (crude)", euro.NPV(), "N/A", "N/A"); } void QMCSobolEx( VanillaOption & euro, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { Size timeSteps = 1; Size nSamples = 32768; // 2^15 boost::shared_ptr<PricingEngine> mcengine2; mcengine2 = MakeMCEuropeanEngine<LowDiscrepancy>(bsmProcess) .withSteps(timeSteps) .withSamples(nSamples); euro.setPricingEngine(mcengine2); PrintResRow("QMC (Sobol)", euro.NPV(), "N/A", "N/A"); } void MCLongstaffSchwartzEx( VanillaOption & amer, boost::shared_ptr<BlackScholesMertonProcess> bsmProcess) { Size mcSeed = 42; boost::shared_ptr<PricingEngine> mcengine3; mcengine3 = MakeMCAmericanEngine<PseudoRandom>(bsmProcess) .withSteps(100) .withAntitheticVariate() .withCalibrationSamples(4096) .withTolerance(0.02) .withSeed(mcSeed); amer.setPricingEngine(mcengine3); PrintResRow("MC (Longstaff Schwartz)", "N/A", "N/A", amer.NPV()); } void EquityOptionEx(void) { std::cout << std::endl; // set up dates Calendar calendar = TARGET(); Date todaysDate(15, May, 1998); Date settlementDate(17, May, 1998); Settings::instance().evaluationDate() = todaysDate; // our options OptionInputs in; in.type = Option::Put; in.underlying = 36; in.strike = 40; in.dividendYield = 0.00; in.riskFreeRate = 0.06; in.volatility = 0.20; in.maturity= Date(17, May, 1999); in.dayCounter = Actual365Fixed(); PrintInputs(std::cout, in); std::cout << std::endl ; // write column headings PrintResRow("Method", "European", "Bermudan", "American"); std::vector<Date> exerciseDates; for (Integer i=1; i<=4; i++) exerciseDates.push_back(settlementDate + 3*i*Months); boost::shared_ptr<Exercise> europeanExercise( new EuropeanExercise(in.maturity)); boost::shared_ptr<Exercise> bermudanExercise( new BermudanExercise(exerciseDates)); boost::shared_ptr<Exercise> americanExercise( new AmericanExercise(settlementDate, in.maturity)); Handle<Quote> underlyingH( boost::shared_ptr<Quote>(new SimpleQuote(in.underlying))); // bootstrap the yield/dividend/vol curves Handle<YieldTermStructure> flatTermStructure( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDate, in.riskFreeRate, in.dayCounter))); Handle<YieldTermStructure> flatDividendTS( boost::shared_ptr<YieldTermStructure>( new FlatForward(settlementDate, in.dividendYield, in.dayCounter))); Handle<BlackVolTermStructure> flatVolTS( boost::shared_ptr<BlackVolTermStructure>( new BlackConstantVol(settlementDate, calendar, in.volatility, in.dayCounter))); boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(in.type, in.strike)); boost::shared_ptr<BlackScholesMertonProcess> bsmProcess( new BlackScholesMertonProcess(underlyingH, flatDividendTS, flatTermStructure, flatVolTS)); // options VanillaOption europeanOption(payoff, europeanExercise); VanillaOption bermudanOption(payoff, bermudanExercise); VanillaOption americanOption(payoff, americanExercise); // Analytic formulas: // Black-Scholes for European BlackScholesEx(europeanOption, bsmProcess); // Barone-Adesi and Whaley approximation for American BaroneAdesiWhaleyEx(americanOption, bsmProcess); // Bjerksund and Stensland approximation for American BjerksundStenslandEx(americanOption, bsmProcess); // Integral IntegralEx(europeanOption, bsmProcess); std::string method; Size timeSteps = 801; // Finite differences FiniteDifferencesEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Jarrow-Rudd BinomialJarrowRuddEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); BinomialCoxRossRubinstein(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Additive equiprobabilities AdditiveEquiprobabilitiesEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Binomial Trigeorgis BinomialTrigeorgisEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Binomial Tian BinomialTianEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Binomial Leisen-Reimer BinomialLeisenReimerEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Binomial method: Binomial Joshi BinomialJoshiEx(europeanOption, bermudanOption, americanOption, bsmProcess, timeSteps); // Monte Carlo Method: MC (crude) MCCrudeEx(europeanOption, bsmProcess); // Monte Carlo Method: QMC (Sobol) QMCSobolEx(europeanOption, bsmProcess); // Monte Carlo Method: MC (Longstaff Schwartz) MCLongstaffSchwartzEx(americanOption, bsmProcess); } int main(int, char* []) { try { boost::timer timer; EquityOptionEx(); Real seconds = timer.elapsed(); Integer hours = int(seconds/3600); seconds -= hours * 3600; Integer minutes = int(seconds/60); seconds -= minutes * 60; std::cout << " \nRun completed in "; if (hours > 0) std::cout << hours << " h "; if (hours > 0 || minutes > 0) std::cout << minutes << " m "; std::cout << std::fixed << std::setprecision(0) << seconds << " s\n" << std::endl; return 0; } catch (std::exception& e) { std::cout << e.what() << std::endl; return 1; } catch (...) { std::cout << "unknown error" << std::endl; return 1; } }