Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commodityswaption.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <oret/toplevelfixture.hpp>
20#include <boost/test/unit_test.hpp>
21#include <boost/timer/timer.hpp>
22#include <iomanip>
23#include <ql/cashflows/simplecashflow.hpp>
24#include <ql/currencies/america.hpp>
25#include <ql/exercise.hpp>
26#include <ql/math/interpolations/linearinterpolation.hpp>
27#include <ql/pricingengines/swap/discountingswapengine.hpp>
28#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/calendars/unitedstates.hpp>
31#include <ql/time/schedule.hpp>
37
38using namespace QuantLib;
39using namespace QuantExt;
40
41using boost::unit_test_framework::test_suite;
42
43BOOST_FIXTURE_TEST_SUITE(OREPlusCommodityTestSuite, ore::test::TopLevelFixture)
44
45BOOST_AUTO_TEST_SUITE(CommoditySwaptionTest)
46
47void run_test(bool averaging) {
48 if (averaging)
49 BOOST_TEST_MESSAGE("Testing Averaging Commodity Swaption Analytical Approximation vs MC Pricing");
50 else
51 BOOST_TEST_MESSAGE("Testing Non-Averaging Commodity Swaption Analytical Approximation vs MC Pricing");
52
53 SavedSettings backup;
54
55 Date today(5, Feb, 2019);
56 Settings::instance().evaluationDate() = today;
57 Calendar cal = UnitedStates(UnitedStates::Settlement);
58
59 // Market - flat price curve
60 std::vector<Date> dates = {today + 1 * Years, today + 2 * Years, today + 3 * Years, today + 4 * Years,
61 today + 5 * Years, today + 7 * Years, today + 10 * Years};
62 std::vector<Real> prices = {100.0, 105.0, 110.0, 115.0, 120.0, 130.0, 150.0};
63 DayCounter dc = Actual365Fixed();
64 Handle<QuantExt::PriceTermStructure> priceCurve(
65 QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(today, dates, prices, dc, USDCurrency()));
66 priceCurve->enableExtrapolation();
67
68 // Market - flat discount curve
69 Handle<Quote> rateQuote(QuantLib::ext::make_shared<SimpleQuote>(0.01));
70 Handle<YieldTermStructure> discountCurve(QuantLib::ext::make_shared<FlatForward>(today, rateQuote, dc, Compounded, Annual));
71
72 // Market - flat volatility structure
73 Handle<QuantLib::BlackVolTermStructure> vol(QuantLib::ext::make_shared<QuantLib::BlackConstantVol>(today, cal, 0.3, dc));
74
75 // Analytical engine
76 Real beta = 0.0;
77 QuantLib::ext::shared_ptr<PricingEngine> analyticalEngine =
78 QuantLib::ext::make_shared<CommoditySwaptionEngine>(discountCurve, vol, beta);
79
80 // Monte Carlo engine
81 Size samples = 10000;
82 QuantLib::ext::shared_ptr<PricingEngine> mcEngine =
83 QuantLib::ext::make_shared<CommoditySwaptionMonteCarloEngine>(discountCurve, vol, samples, beta);
84
85 QuantLib::ext::shared_ptr<PricingEngine> swapEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(discountCurve);
86
87 Real quantity = 1.0;
88 std::string name = "CL";
89 QuantLib::ext::shared_ptr<CommoditySpotIndex> index = QuantLib::ext::make_shared<CommoditySpotIndex>(name, cal, priceCurve);
90
91 // Swap start times
92 std::vector<Size> startTimes = {0, 1, 2, 3, 4, 5, 7, 10};
93 std::vector<Real> strikes = {10.0, 60.0, 80.0, 100.0, 120.0, 140.0};
94
95 for (Size k = 0; k < strikes.size(); ++k) {
96
97 Real strikePrice = strikes[k];
98
99 // Vary Swaption start dates, set up underlying Swaps of length one year with 12 monthly calculation periods
100 for (Size i = 0; i < startTimes.size(); ++i) {
101 Period startTerm = (i == 0 ? 6 * Months : startTimes[i] * Years);
102 Date start = today + startTerm;
103 Date end = start + 1 * Years;
104 Schedule schedule = MakeSchedule().from(start).to(end).withTenor(1 * Months);
105 Leg fixedLeg;
106 for (Size j = 1; j < schedule.size(); ++j)
107 fixedLeg.push_back(QuantLib::ext::make_shared<SimpleCashFlow>(quantity * strikePrice, schedule[j]));
108 Leg floatLeg;
109 if (averaging)
110 floatLeg = CommodityIndexedAverageLeg(schedule, index)
111 .withQuantities(quantity)
114 .withSpreads(0.0);
115 else
116 floatLeg = CommodityIndexedLeg(schedule, index)
117 .withQuantities(quantity)
120 .withSpreads(0.0);
121
122 QuantLib::ext::shared_ptr<QuantLib::Swap> payerSwap = QuantLib::ext::make_shared<QuantLib::Swap>(fixedLeg, floatLeg);
123 QuantLib::ext::shared_ptr<QuantLib::Swap> receiverSwap = QuantLib::ext::make_shared<QuantLib::Swap>(floatLeg, fixedLeg);
124
125 payerSwap->setPricingEngine(swapEngine);
126 receiverSwap->setPricingEngine(swapEngine);
127 BOOST_TEST_MESSAGE("Testing Swap NPV " << payerSwap->NPV() << " " << receiverSwap->NPV());
128
129 QuantLib::ext::shared_ptr<EuropeanExercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(start);
130 QuantLib::ext::shared_ptr<QuantExt::GenericSwaption> payerSwaption =
131 QuantLib::ext::make_shared<QuantExt::GenericSwaption>(payerSwap, exercise);
132 QuantLib::ext::shared_ptr<QuantExt::GenericSwaption> receiverSwaption =
133 QuantLib::ext::make_shared<QuantExt::GenericSwaption>(receiverSwap, exercise);
134
135 boost::timer::cpu_timer tan;
136 payerSwaption->setPricingEngine(analyticalEngine);
137 receiverSwaption->setPricingEngine(analyticalEngine);
138 Real anPayerPrice = payerSwaption->NPV();
139 Real anReceiverPrice = receiverSwaption->NPV();
140 Real anTime = tan.elapsed().wall * 1e-6;
141
142 boost::timer::cpu_timer tmc;
143 payerSwaption->setPricingEngine(mcEngine);
144 receiverSwaption->setPricingEngine(mcEngine);
145 Real mcPayerPrice = payerSwaption->NPV();
146 Real mcReceiverPrice = receiverSwaption->NPV();
147 Real mcTime = tmc.elapsed().wall * 1e-6;
148
149 Real payerError1 = 100.0 * fabs(mcPayerPrice - anPayerPrice) / mcPayerPrice;
150 Real payerError2 = 100.0 * fabs(mcPayerPrice - anPayerPrice) / anPayerPrice;
151 Real receiverError1 = 100.0 * fabs(mcReceiverPrice - anReceiverPrice) / mcReceiverPrice;
152 Real receiverError2 = 100.0 * fabs(mcReceiverPrice - anReceiverPrice) / anReceiverPrice;
153
154 BOOST_TEST_MESSAGE("Analytical vs MC, Payer Swaption, "
155 << std::fixed << std::setprecision(2) << "strike " << strikePrice << ", expiry "
156 << startTerm << ": "
157 << "an " << anPayerPrice << " mc " << mcPayerPrice << " diff "
158 << anPayerPrice - mcPayerPrice << " rel " << std::max(payerError1, payerError2) << "% "
159 << " underlying " << payerSwap->NPV() << " (" << anTime << " ms, " << mcTime << " ms)");
160
161 BOOST_TEST_MESSAGE(
162 "Analytical vs MC, Receiver Swaption, "
163 << std::fixed << std::setprecision(2) << "strike " << strikePrice << ", expiry " << startTerm << ": "
164 << "an " << anReceiverPrice << " mc " << mcReceiverPrice << " diff "
165 << anReceiverPrice - mcReceiverPrice << " rel " << std::max(receiverError1, receiverError2) << "% "
166 << " underlying " << receiverSwap->NPV() << " (" << anTime << " ms, " << mcTime << " ms)");
167
168 // Absolute tolerance is generous, and even if the following check is passed:
169 // Relative errors for short expiry options are significant, in particular out of the money.
170 // => The analytical approximation is rough, consider using the MC engine if performance permits.
171 BOOST_CHECK_SMALL(anPayerPrice - mcPayerPrice, 26.0);
172 BOOST_CHECK_SMALL(anReceiverPrice - mcReceiverPrice, 26.0);
173
174 Real anPutCallPartiy = anPayerPrice - anReceiverPrice - payerSwap->NPV();
175 Real mcPutCallPartiy = mcPayerPrice - mcReceiverPrice - payerSwap->NPV();
176
177 BOOST_TEST_MESSAGE("put/call parity check, "
178 << std::fixed << std::setprecision(2) << strikePrice << " " << startTerm
179 << ": analyticalPayerSwaption - analyticalreceiverSwaption - payerSwap = "
180 << anPutCallPartiy);
181 BOOST_TEST_MESSAGE("put/call parity check, "
182 << std::fixed << std::setprecision(2) << strikePrice << " " << startTerm
183 << ": mcPayerSwaption - mcreceiverSwaption - payerSwap = " << mcPutCallPartiy);
184
185 // Put/call partiy check tolerances are tight on both cases
186 BOOST_CHECK_SMALL(anPutCallPartiy, 0.5);
187 BOOST_CHECK_SMALL(mcPutCallPartiy, 4.0);
188 }
189 }
190}
191
192BOOST_AUTO_TEST_CASE(testAveragingCommoditySwaption) {
193 // averaging swaption
194 run_test(true);
195}
196
197BOOST_AUTO_TEST_CASE(testNonAveragingCommoditySwaption) {
198 // non-averaging swaption
199 run_test(false);
200}
201
202BOOST_AUTO_TEST_SUITE_END()
203BOOST_AUTO_TEST_SUITE_END()
CommodityIndexedAverageLeg & withQuantities(QuantLib::Real quantity)
CommodityIndexedAverageLeg & withPaymentCalendar(const QuantLib::Calendar &paymentCalendar)
CommodityIndexedAverageLeg & withPricingCalendar(const QuantLib::Calendar &pricingCalendar)
CommodityIndexedAverageLeg & withSpreads(QuantLib::Real spread)
CommodityIndexedLeg & withPricingLagCalendar(const QuantLib::Calendar &pricingLagCalendar)
CommodityIndexedLeg & withQuantities(QuantLib::Real quantity)
CommodityIndexedLeg & withPaymentCalendar(const QuantLib::Calendar &paymentCalendar)
CommodityIndexedLeg & withSpreads(QuantLib::Real spread)
vector< Real > strikes
void run_test(bool averaging)
BOOST_AUTO_TEST_CASE(testAveragingCommoditySwaption)
string name