/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2003, 2004 Ferdinando Ametrano Copyright (C) 2005 StatPro Italia srl 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 . The license is also available online at . 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. */ #include "asianoptions.hpp" #include "utilities.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace QuantLib; using namespace boost::unit_test_framework; #define REPORT_FAILURE(greekName, averageType, \ runningAccumulator, pastFixings, \ fixingDates, payoff, exercise, s, q, r, today, v, \ expected, calculated, tolerance) \ BOOST_ERROR( \ exerciseTypeToString(exercise) \ << " Asian option with " \ << averageTypeToString(averageType) << " and " \ << payoffTypeToString(payoff) << " payoff:\n" \ << " running variable: " \ << io::checknull(runningAccumulator) << "\n" \ << " past fixings: " \ << io::checknull(pastFixings) << "\n" \ << " future fixings: " << fixingDates.size() << "\n" \ << " underlying value: " << s << "\n" \ << " strike: " << payoff->strike() << "\n" \ << " dividend yield: " << io::rate(q) << "\n" \ << " risk-free rate: " << io::rate(r) << "\n" \ << " reference date: " << today << "\n" \ << " maturity: " << exercise->lastDate() << "\n" \ << " volatility: " << io::volatility(v) << "\n\n" \ << " expected " << greekName << ": " << expected << "\n" \ << " calculated " << greekName << ": " << calculated << "\n"\ << " error: " << std::fabs(expected-calculated) \ << "\n" \ << " tolerance: " << tolerance); QL_BEGIN_TEST_LOCALS(AsianOptionTest) std::string averageTypeToString(Average::Type averageType) { if (averageType == Average::Geometric) return "Geometric Averaging"; else if (averageType == Average::Arithmetic) return "Arithmetic Averaging"; else QL_FAIL("unknown averaging"); } void teardown() { Settings::instance().evaluationDate() = Date(); } QL_END_TEST_LOCALS(AsianOptionTest) void AsianOptionTest::testAnalyticContinuousGeometricAveragePrice() { BOOST_MESSAGE("Testing analytic continuous geometric average-price " "Asians..."); // data from "Option Pricing Formulas", Haug, pag.96-97 DayCounter dc = Actual360(); Date today = Date::todaysDate(); boost::shared_ptr spot(new SimpleQuote(80.0)); boost::shared_ptr qRate(new SimpleQuote(-0.03)); boost::shared_ptr qTS = flatRate(today, qRate, dc); boost::shared_ptr rRate(new SimpleQuote(0.05)); boost::shared_ptr rTS = flatRate(today, rRate, dc); boost::shared_ptr vol(new SimpleQuote(0.20)); boost::shared_ptr volTS = flatVol(today, vol, dc); boost::shared_ptr stochProcess(new BlackScholesMertonProcess(Handle(spot), Handle(qTS), Handle(rTS), Handle(volTS))); boost::shared_ptr engine(new AnalyticContinuousGeometricAveragePriceAsianEngine); Average::Type averageType = Average::Geometric; Option::Type type = Option::Put; Real strike = 85.0; Date exerciseDate = today + 90; Size pastFixings = Null(); Real runningAccumulator = Null(); boost::shared_ptr payoff( new PlainVanillaPayoff(type, strike)); boost::shared_ptr exercise(new EuropeanExercise(exerciseDate)); ContinuousAveragingAsianOption option(averageType, stochProcess, payoff, exercise, engine); Real calculated = option.NPV(); Real expected = 4.6922; Real tolerance = 1.0e-4; if (std::fabs(calculated-expected) > tolerance) { REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings, std::vector(), payoff, exercise, spot->value(), qRate->value(), rRate->value(), today, vol->value(), expected, calculated, tolerance); } // trying to approximate the continuous version with the discrete version runningAccumulator = 1.0; pastFixings = 0; std::vector fixingDates(exerciseDate-today+1); for (Size i=0; i engine2(new AnalyticDiscreteGeometricAveragePriceAsianEngine); DiscreteAveragingAsianOption option2(averageType, runningAccumulator, pastFixings, fixingDates, stochProcess, payoff, exercise, engine2); calculated = option2.NPV(); tolerance = 3.0e-3; if (std::fabs(calculated-expected) > tolerance) { REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings, fixingDates, payoff, exercise, spot->value(), qRate->value(), rRate->value(), today, vol->value(), expected, calculated, tolerance); } } void AsianOptionTest::testAnalyticContinuousGeometricAveragePriceGreeks() { BOOST_MESSAGE("Testing analytic continuous geometric average-price Asian " "greeks..."); QL_TEST_BEGIN std::map calculated, expected, tolerance; tolerance["delta"] = 1.0e-5; tolerance["gamma"] = 1.0e-5; tolerance["theta"] = 1.0e-5; tolerance["rho"] = 1.0e-5; tolerance["divRho"] = 1.0e-5; tolerance["vega"] = 1.0e-5; Option::Type types[] = { Option::Call, Option::Put }; Real underlyings[] = { 100.0 }; Real strikes[] = { 90.0, 100.0, 110.0 }; Rate qRates[] = { 0.04, 0.05, 0.06 }; Rate rRates[] = { 0.01, 0.05, 0.15 }; Integer lengths[] = { 1, 2 }; Volatility vols[] = { 0.11, 0.50, 1.20 }; DayCounter dc = Actual360(); Date today = Date::todaysDate(); Settings::instance().evaluationDate() = today; boost::shared_ptr spot(new SimpleQuote(0.0)); boost::shared_ptr qRate(new SimpleQuote(0.0)); Handle qTS(flatRate(qRate, dc)); boost::shared_ptr rRate(new SimpleQuote(0.0)); Handle rTS(flatRate(rRate, dc)); boost::shared_ptr vol(new SimpleQuote(0.0)); Handle volTS(flatVol(vol, dc)); boost::shared_ptr process( new BlackScholesMertonProcess(Handle(spot), qTS, rTS, volTS)); for (Size i=0; i maturity( new EuropeanExercise(today + lengths[k]*Years)); boost::shared_ptr payoff( new PlainVanillaPayoff(types[i], strikes[j])); boost::shared_ptr engine(new AnalyticContinuousGeometricAveragePriceAsianEngine); ContinuousAveragingAsianOption option(Average::Geometric, process, payoff, maturity, engine); Size pastFixings = Null(); Real runningAverage = Null(); for (Size l=0; lsetValue(u); qRate->setValue(q); rRate->setValue(r); vol->setValue(v); Real value = option.NPV(); calculated["delta"] = option.delta(); calculated["gamma"] = option.gamma(); calculated["theta"] = option.theta(); calculated["rho"] = option.rho(); calculated["divRho"] = option.dividendRho(); calculated["vega"] = option.vega(); if (value > spot->value()*1.0e-5) { // perturb spot and get delta and gamma Real du = u*1.0e-4; spot->setValue(u+du); Real value_p = option.NPV(), delta_p = option.delta(); spot->setValue(u-du); Real value_m = option.NPV(), delta_m = option.delta(); spot->setValue(u); expected["delta"] = (value_p - value_m)/(2*du); expected["gamma"] = (delta_p - delta_m)/(2*du); // perturb rates and get rho and dividend rho Spread dr = r*1.0e-4; rRate->setValue(r+dr); value_p = option.NPV(); rRate->setValue(r-dr); value_m = option.NPV(); rRate->setValue(r); expected["rho"] = (value_p - value_m)/(2*dr); Spread dq = q*1.0e-4; qRate->setValue(q+dq); value_p = option.NPV(); qRate->setValue(q-dq); value_m = option.NPV(); qRate->setValue(q); expected["divRho"] = (value_p - value_m)/(2*dq); // perturb volatility and get vega Volatility dv = v*1.0e-4; vol->setValue(v+dv); value_p = option.NPV(); vol->setValue(v-dv); value_m = option.NPV(); vol->setValue(v); expected["vega"] = (value_p - value_m)/(2*dv); // perturb date and get theta Time dT = dc.yearFraction(today-1, today+1); Settings::instance().evaluationDate() = today-1; value_m = option.NPV(); Settings::instance().evaluationDate() = today+1; value_p = option.NPV(); Settings::instance().evaluationDate() = today; expected["theta"] = (value_p - value_m)/dT; // compare std::map::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,u); if (error>tol) { REPORT_FAILURE(greek, Average::Geometric, runningAverage, pastFixings, std::vector(), payoff, maturity, u, q, r, today, v, expct, calcl, tol); } } } } } } } } } } QL_TEST_TEARDOWN } void AsianOptionTest::testAnalyticDiscreteGeometricAveragePrice() { BOOST_MESSAGE("Testing analytic discrete geometric average-price " "Asians..."); // data from "Implementing Derivatives Model", // Clewlow, Strickland, p.118-123 DayCounter dc = Actual360(); Date today = Date::todaysDate(); boost::shared_ptr spot(new SimpleQuote(100.0)); boost::shared_ptr qRate(new SimpleQuote(0.03)); boost::shared_ptr qTS = flatRate(today, qRate, dc); boost::shared_ptr rRate(new SimpleQuote(0.06)); boost::shared_ptr rTS = flatRate(today, rRate, dc); boost::shared_ptr vol(new SimpleQuote(0.20)); boost::shared_ptr volTS = flatVol(today, vol, dc); boost::shared_ptr stochProcess(new BlackScholesMertonProcess(Handle(spot), Handle(qTS), Handle(rTS), Handle(volTS))); boost::shared_ptr engine(new AnalyticDiscreteGeometricAveragePriceAsianEngine); Average::Type averageType = Average::Geometric; Real runningAccumulator = 1.0; Size pastFixings = 0; Size futureFixings = 10; Option::Type type = Option::Call; Real strike = 100.0; boost::shared_ptr payoff( new PlainVanillaPayoff(type, strike)); Date exerciseDate = today + 360; boost::shared_ptr exercise(new EuropeanExercise(exerciseDate)); std::vector fixingDates(futureFixings); Integer dt = Integer(360/futureFixings+0.5); fixingDates[0] = today + dt; for (Size j=1; j tolerance) { REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings, fixingDates, payoff, exercise, spot->value(), qRate->value(), rRate->value(), today, vol->value(), expected, calculated, tolerance); } } void AsianOptionTest::testMCDiscreteGeometricAveragePrice() { BOOST_MESSAGE("Testing Monte Carlo discrete geometric average-price " "Asians..."); // data from "Implementing Derivatives Model", // Clewlow, Strickland, p.118-123 DayCounter dc = Actual360(); Date today = Date::todaysDate(); boost::shared_ptr spot(new SimpleQuote(100.0)); boost::shared_ptr qRate(new SimpleQuote(0.03)); boost::shared_ptr qTS = flatRate(today, qRate, dc); boost::shared_ptr rRate(new SimpleQuote(0.06)); boost::shared_ptr rTS = flatRate(today, rRate, dc); boost::shared_ptr vol(new SimpleQuote(0.20)); boost::shared_ptr volTS = flatVol(today, vol, dc); boost::shared_ptr stochProcess(new BlackScholesMertonProcess(Handle(spot), Handle(qTS), Handle(rTS), Handle(volTS))); Real tolerance = 4.0e-3; boost::shared_ptr engine; engine = MakeMCDiscreteGeometricAPEngine().withStepsPerYear(1) .withSamples(8191); /* MakeMCDiscreteGeometricAPEngine().withStepsPerYear(1) .withTolerance(tolerance/4.0) .withAntitheticVariate() .withSeed(42); */ Average::Type averageType = Average::Geometric; Real runningAccumulator = 1.0; Size pastFixings = 0; Size futureFixings = 10; Option::Type type = Option::Call; Real strike = 100.0; boost::shared_ptr payoff( new PlainVanillaPayoff(type, strike)); Date exerciseDate = today + 360; boost::shared_ptr exercise(new EuropeanExercise(exerciseDate)); std::vector fixingDates(futureFixings); Integer dt = Integer(360/futureFixings+0.5); fixingDates[0] = today + dt; for (Size j=1; j engine2(new AnalyticDiscreteGeometricAveragePriceAsianEngine); option.setPricingEngine(engine2); Real expected = option.NPV(); //5.3425606635; if (std::fabs(calculated-expected) > tolerance) { REPORT_FAILURE("value", averageType, runningAccumulator, pastFixings, fixingDates, payoff, exercise, spot->value(), qRate->value(), rRate->value(), today, vol->value(), expected, calculated, tolerance); } } QL_BEGIN_TEST_LOCALS(AsianOptionTest) struct DiscreteAverageData { Option::Type type; Real underlying; Real strike; Rate dividendYield; Rate riskFreeRate; Time first; Time length; Size fixings; Volatility volatility; bool controlVariate; Real result; }; QL_END_TEST_LOCALS(AsianOptionTest) void AsianOptionTest::testMCDiscreteArithmeticAveragePrice() { BOOST_MESSAGE("Testing Monte Carlo discrete arithmetic average-price " "Asians..."); QL_TEST_START_TIMING // data from "Asian Option", Levy, 1997 // in "Exotic Options: The State of the Art", // edited by Clewlow, Strickland DiscreteAverageData cases4[] = { { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 2, 0.13, true, 1.3942835683 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 4, 0.13, true, 1.5852442983 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 8, 0.13, true, 1.66970673 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 12, 0.13, true, 1.6980019214 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 26, 0.13, true, 1.7255070456 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 52, 0.13, true, 1.7401553533 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 100, 0.13, true, 1.7478303712 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 250, 0.13, true, 1.7490291943 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 500, 0.13, true, 1.7515113291 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0/12.0, 1000, 0.13, true, 1.7537344885 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 2, 0.13, true, 1.8496053697 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 4, 0.13, true, 2.0111495205 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 8, 0.13, true, 2.0852138818 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 12, 0.13, true, 2.1105094397 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 26, 0.13, true, 2.1346526695 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 52, 0.13, true, 2.147489651 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 100, 0.13, true, 2.154728109 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 250, 0.13, true, 2.1564276565 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 500, 0.13, true, 2.1594238588 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0/12.0, 11.0/12.0, 1000, 0.13, true, 2.1595367326 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 2, 0.13, true, 2.63315092584 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 4, 0.13, true, 2.76723962361 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 8, 0.13, true, 2.83124836881 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 12, 0.13, true, 2.84290301412 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 26, 0.13, true, 2.88179560417 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 52, 0.13, true, 2.88447044543 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 100, 0.13, true, 2.89985329603 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 250, 0.13, true, 2.90047296063 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 500, 0.13, true, 2.89813412160 }, { Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0/12.0, 11.0/12.0, 1000, 0.13, true, 2.89703362437 } }; DayCounter dc = Actual360(); Date today = Date::todaysDate(); boost::shared_ptr spot(new SimpleQuote(100.0)); boost::shared_ptr qRate(new SimpleQuote(0.03)); boost::shared_ptr qTS = flatRate(today, qRate, dc); boost::shared_ptr rRate(new SimpleQuote(0.06)); boost::shared_ptr rTS = flatRate(today, rRate, dc); boost::shared_ptr vol(new SimpleQuote(0.20)); boost::shared_ptr volTS = flatVol(today, vol, dc); boost::shared_ptr engine; engine = /* MakeMCDiscreteArithmeticAPEngine().withStepsPerYear(1) .withTolerance(tolerance/4.0) .withControlVariate() .withAntitheticVariate() .withSeed(42); */ MakeMCDiscreteArithmeticAPEngine().withStepsPerYear(1) .withSamples(2047) .withControlVariate(); Average::Type averageType = Average::Arithmetic; Real runningSum = 0.0; Size pastFixings = 0; for (Size l=0; l payoff(new PlainVanillaPayoff(cases4[l].type, cases4[l].strike)); Time dt = cases4[l].length/(cases4[l].fixings-1); std::vector