21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
24#include <ql/currencies/america.hpp>
25#include <ql/math/interpolations/linearinterpolation.hpp>
26#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
27#include <ql/quotes/simplequote.hpp>
28#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
35using namespace boost::unit_test_framework;
40using std::setprecision;
49Handle<YieldTermStructure> flatYts(Rate r) {
50 return Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), r, Actual365Fixed()));
54QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> getProcess(Rate spot, Volatility vol, Rate r, Rate q) {
57 Handle<Quote> spotQuote(QuantLib::ext::make_shared<SimpleQuote>(spot));
58 Handle<YieldTermStructure> rTs = flatYts(r);
59 Handle<YieldTermStructure> qTs = flatYts(q);
60 Handle<BlackVolTermStructure> volTs(QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), vol, Actual365Fixed()));
63 return QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(spotQuote, qTs, rTs, volTs);
67Handle<PriceTermStructure> priceTs() {
68 vector<Period> tenors{ 0 * Days, 1 * Years };
69 vector<Real> prices{ 60.0, 69.0 };
70 return Handle<PriceTermStructure>(
79 mp[
"npv"] = option.NPV();
82 mp[
"delta"] = option.delta();
83 mp[
"deltaForward"] = option.deltaForward();
84 mp[
"elasticity"] = option.elasticity();
85 mp[
"gamma"] = option.gamma();
86 mp[
"theta"] = option.theta();
87 mp[
"thetaPerDay"] = option.thetaPerDay();
88 mp[
"vega"] = option.vega();
89 mp[
"rho"] = option.rho();
90 mp[
"dividendRho"] = option.dividendRho();
101 auto yts = flatYts(r);
102 DiscountFactor df_tp = yts->discount(option.
paymentDate());
103 Time tp = yts->timeFromReference(option.
paymentDate());
104 BOOST_TEST_MESSAGE(
"Discount factor from payment is: " << fixed << setprecision(12) << df_tp);
107 Real valueAtExpiry = (*option.payoff())(exercisePrice);
110 map<string, Real> cashSettledResults = results(option);
111 BOOST_REQUIRE(cashSettledResults.count(
"npv") == 1);
112 for (
const auto& kv : cashSettledResults) {
113 BOOST_TEST_CONTEXT(
"With result " << kv.first) {
114 BOOST_TEST_MESSAGE(
"Value for " << kv.first <<
" with cash settlement is: " << fixed << setprecision(12)
116 if (kv.first ==
"npv") {
117 BOOST_CHECK_SMALL(kv.second - df_tp * valueAtExpiry, tolerance);
118 }
else if (kv.first ==
"rho") {
119 BOOST_CHECK_SMALL(kv.second + tp * cashSettledResults.at(
"npv"), tolerance);
120 }
else if (kv.first ==
"theta") {
121 if (tp > 0.0 && !close(tp, 0.0)) {
122 BOOST_CHECK_SMALL(kv.second + std::log(df_tp) / tp * cashSettledResults.at(
"npv"), tolerance);
124 BOOST_CHECK(close(kv.second, 0.0));
126 }
else if (kv.first ==
"thetaPerDay") {
127 if (cashSettledResults.count(
"theta") == 0) {
128 BOOST_ERROR(
"Expected results to contain a value for theta");
131 BOOST_CHECK_SMALL(kv.second - cashSettledResults.at(
"theta") / 365.0, tolerance);
134 BOOST_CHECK(close(kv.second, 0.0));
144BOOST_AUTO_TEST_SUITE(AnalyticCashSettledEuropeanEngineTest)
147vector<Option::Type>
optionTypes{ Option::Type::Call, Option::Type::Put };
151 BOOST_TEST_MESSAGE(
"Testing cash settled option pricing before expiry...");
153 Settings::instance().evaluationDate() = Date(3, Jun, 2020);
156 Date expiry(3, Sep, 2020);
157 Date payment(7, Sep, 2020);
158 bool automaticExercise =
false;
163 Volatility vol = 0.30;
166 QuantLib::ext::shared_ptr<PricingEngine> engine =
167 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
170 option.setPricingEngine(engine);
171 map<string, Real> cashSettledResults = results(option);
174 engine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(getProcess(spot, vol, r, q));
175 option.setPricingEngine(engine);
176 map<string, Real> theoreticalResults = results(option);
179 auto yts = flatYts(r);
180 DiscountFactor df_te_tp = yts->discount(payment) / yts->discount(expiry);
181 BOOST_TEST_MESSAGE(
"Discount factor from payment to expiry is: " << fixed << setprecision(12) << df_te_tp);
184 Real tolerance = 1e-12;
185 BOOST_REQUIRE_EQUAL(cashSettledResults.size(), theoreticalResults.size());
186 BOOST_REQUIRE(cashSettledResults.count(
"npv") == 1);
187 BOOST_REQUIRE(theoreticalResults.count(
"npv") == 1);
188 for (
const auto& kv : cashSettledResults) {
190 BOOST_TEST_CONTEXT(
"With result " << kv.first) {
192 auto it = theoreticalResults.find(kv.first);
193 BOOST_CHECK(it != theoreticalResults.end());
196 if (it == theoreticalResults.end())
200 Real theorResult = it->second;
201 BOOST_TEST_MESSAGE(
"Value for " << kv.first <<
" with cash settlement is: " << fixed << setprecision(12)
203 BOOST_TEST_MESSAGE(
"Value for " << kv.first <<
" ignoring cash settlement is: " << fixed << setprecision(12)
208 if (close(theorResult, 0.0)) {
209 BOOST_CHECK(close(kv.second, 0.0));
210 }
else if (kv.first ==
"elasticity" || kv.first ==
"itmCashProbability") {
211 BOOST_CHECK(close(kv.second, theorResult));
212 }
else if (kv.first ==
"rho") {
213 Time delta_te_tp = yts->timeFromReference(payment) - yts->timeFromReference(expiry);
214 Real expRho = df_te_tp * (theorResult - delta_te_tp * theoreticalResults.at(
"npv"));
215 BOOST_TEST_MESSAGE(
"Value for expected rho is: " << fixed << setprecision(12) << expRho);
216 BOOST_CHECK(close(kv.second, expRho));
218 BOOST_CHECK_SMALL(kv.second / theorResult - df_te_tp, tolerance);
227 BOOST_TEST_MESSAGE(
"Testing cash settled manual exercise option pricing after expiry...");
229 Settings::instance().evaluationDate() = Date(4, Sep, 2020);
232 Date expiry(3, Sep, 2020);
233 Date payment(7, Sep, 2020);
234 bool automaticExercise =
false;
239 Volatility vol = 0.30;
242 QuantLib::ext::shared_ptr<PricingEngine> engine =
243 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
246 option.setPricingEngine(engine);
247 map<string, Real> cashSettledResults = results(option);
250 for (
const auto& kv : cashSettledResults) {
251 BOOST_TEST_CONTEXT(
"With result " << kv.first) { BOOST_CHECK(close(kv.second, 0.0)); }
255 Real exercisePrice = 59.00;
259 checkOptionValues(option, r, exercisePrice);
268 BOOST_TEST_MESSAGE(
"Testing cash settled manual exercise option on expiry date...");
271 Settings::instance().includeReferenceDateEvents() = irde;
274 Date expiry(3, Sep, 2020);
275 Settings::instance().evaluationDate() = expiry;
276 Date payment(7, Sep, 2020);
277 bool automaticExercise =
false;
282 Volatility vol = 0.30;
285 QuantLib::ext::shared_ptr<PricingEngine> engine =
286 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
289 option.setPricingEngine(engine);
293 checkOptionValues(option, r, spot);
296 Real exercisePrice = 59.00;
300 checkOptionValues(option, r, exercisePrice);
306 BOOST_TEST_MESSAGE(
"Testing cash settled manual exercise option on payment date...");
309 Date expiry(3, Sep, 2020);
310 Date payment(7, Sep, 2020);
311 Settings::instance().evaluationDate() = payment;
312 bool automaticExercise =
false;
317 Volatility vol = 0.30;
320 QuantLib::ext::shared_ptr<PricingEngine> engine =
321 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
324 option.setPricingEngine(engine);
327 Real exercisePrice = 59.00;
331 Settings::instance().includeReferenceDateEvents() =
true;
334 checkOptionValues(option, r, exercisePrice);
338 Settings::instance().includeReferenceDateEvents() =
false;
339 option.recalculate();
342 map<string, Real> cashSettledResults = results(option);
343 for (
const auto& kv : cashSettledResults) {
344 BOOST_TEST_CONTEXT(
"With result " << kv.first) { BOOST_CHECK(close(kv.second, 0.0)); }
351 BOOST_TEST_MESSAGE(
"Testing cash settled automatic exercise option pricing after expiry...");
353 Settings::instance().evaluationDate() = Date(4, Sep, 2020);
356 Date expiry(3, Sep, 2020);
357 NullCalendar fixingCalendar;
358 QuantLib::ext::shared_ptr<Index> index =
359 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"TEST", expiry, fixingCalendar, priceTs());
362 Real exercisePrice = 59.00;
363 index->addFixing(expiry, exercisePrice);
366 Date payment(7, Sep, 2020);
367 bool automaticExercise =
true;
372 Volatility vol = 0.30;
375 QuantLib::ext::shared_ptr<PricingEngine> engine =
376 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
379 option.setPricingEngine(engine);
382 checkOptionValues(option, r, exercisePrice);
388 BOOST_TEST_MESSAGE(
"Testing cash settled automatic exercise option pricing on expiry...");
391 Settings::instance().includeReferenceDateEvents() = irde;
394 Date expiry(3, Sep, 2020);
395 Settings::instance().evaluationDate() = expiry;
396 NullCalendar fixingCalendar;
397 Handle<PriceTermStructure> pts = priceTs();
398 QuantLib::ext::shared_ptr<Index> index = QuantLib::ext::make_shared<CommodityFuturesIndex>(
"TEST", expiry, fixingCalendar, pts);
401 Date payment(7, Sep, 2020);
402 bool automaticExercise =
true;
407 Volatility vol = 0.30;
410 QuantLib::ext::shared_ptr<PricingEngine> engine =
411 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
414 option.setPricingEngine(engine);
418 Real ptsPrice = pts->price(0.0);
419 checkOptionValues(option, r, ptsPrice);
422 Real exercisePrice = 59.00;
423 index->addFixing(expiry, exercisePrice);
426 checkOptionValues(option, r, exercisePrice);
432 BOOST_TEST_MESSAGE(
"Testing cash settled automatic exercise option pricing on payment date...");
435 Date expiry(3, Sep, 2020);
436 NullCalendar fixingCalendar;
437 QuantLib::ext::shared_ptr<Index> index =
438 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"TEST", expiry, fixingCalendar, priceTs());
441 Real exercisePrice = 59.00;
442 index->addFixing(expiry, exercisePrice);
445 Date payment(7, Sep, 2020);
446 Settings::instance().evaluationDate() = payment;
447 bool automaticExercise =
true;
452 Volatility vol = 0.30;
455 QuantLib::ext::shared_ptr<PricingEngine> engine =
456 QuantLib::ext::make_shared<AnalyticCashSettledEuropeanEngine>(getProcess(spot, vol, r, q));
459 option.setPricingEngine(engine);
462 Settings::instance().includeReferenceDateEvents() =
true;
465 checkOptionValues(option, r, exercisePrice);
469 Settings::instance().includeReferenceDateEvents() =
false;
470 option.recalculate();
473 map<string, Real> cashSettledResults = results(option);
474 for (
const auto& kv : cashSettledResults) {
475 BOOST_TEST_CONTEXT(
"With result " << kv.first) { BOOST_CHECK(close(kv.second, 0.0)); }
479BOOST_AUTO_TEST_SUITE_END()
481BOOST_AUTO_TEST_SUITE_END()
pricing engine for cash settled European vanilla options.
const QuantLib::Date & paymentDate() const
void exercise(QuantLib::Real priceAtExercise)
Mark option as manually exercised at the given priceAtExercise.
Interpolated price curve.
commodity index class for holding commodity spot and futures price histories and forwarding.
Interpolated price curve.
BOOST_DATA_TEST_CASE(testOptionBeforeExpiry, bdata::make(strikes) *bdata::make(optionTypes), strike, optionType)
vector< Option::Type > optionTypes
Fixture that can be used at top level.