Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commodityspreadoption.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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 "toplevelfixture.hpp"
20#include <boost/test/unit_test.hpp>
21#include <ql/currencies/america.hpp>
22#include <ql/math/interpolations/linearinterpolation.hpp>
23#include <ql/math/matrix.hpp>
24#include <ql/pricingengines/blackformula.hpp>
25#include <ql/settings.hpp>
26#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
27#include <ql/termstructures/yield/flatforward.hpp>
28#include <ql/time/calendars/nullcalendar.hpp>
35
36using namespace std;
37using namespace boost::unit_test_framework;
38using namespace QuantLib;
39using namespace QuantExt;
40
41namespace {
42
43class MockUpExpiryCalculator : public FutureExpiryCalculator {
44public:
45 // Inherited via FutureExpiryCalculator
46 virtual QuantLib::Date nextExpiry(bool includeExpiry, const QuantLib::Date& referenceDate, QuantLib::Natural offset,
47 bool forOption) override {
48 return Date::endOfMonth(referenceDate);
49 }
50 virtual QuantLib::Date priorExpiry(bool includeExpiry, const QuantLib::Date& referenceDate,
51 bool forOption) override {
52 return Date(1, referenceDate.month(), referenceDate.year()) - 1 * Days;
53 }
54 virtual QuantLib::Date expiryDate(const QuantLib::Date& contractDate, QuantLib::Natural monthOffset,
55 bool forOption) override {
56 return Date::endOfMonth(contractDate);
57 }
58 virtual QuantLib::Date contractDate(const QuantLib::Date& expiryDate) override { return expiryDate; }
59 virtual QuantLib::Date applyFutureMonthOffset(const QuantLib::Date& contractDate,
60 Natural futureMonthOffset) override {
61 return QuantLib::Date();
62 }
63};
64
65double monteCarloPricing(double F1, double F2, double sigma1, double sigma2, double rho, double ttm, double df,
66 double strike) {
67 constexpr size_t seed = 42;
68 constexpr size_t samples = 100000;
69 Matrix L(2, 2, 0);
70 L[0][0] = sigma1;
71 L[1][0] = rho * sigma2;
72 L[1][1] = std::sqrt(1.0 - rho * rho) * sigma2;
73 Array F(2, 0);
74 Array Z(2, 0);
75 Array sigma(2, 0);
76 F[0] = std::log(F1);
77 F[1] = std::log(F2);
78 sigma[0] = sigma1;
79 sigma[1] = sigma2;
80
81 double payoff = 0;
82 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(2, seed);
83 Array drift = -0.5 * sigma * sigma * ttm;
84 for (size_t i = 0; i < samples; i++) {
85 auto sample = rsg.nextSequence().value;
86 std::copy(sample.begin(), sample.end(), Z.begin());
87 Array Ft = F + drift + L * Z * std::sqrt(ttm);
88 payoff += std::max(std::exp(Ft[0]) - std::exp(Ft[1]) - strike, 0.0);
89 }
90
91 return payoff * df / (double)samples;
92}
93
94double monteCarloPricingSpotAveraging(const ext::shared_ptr<CommodityIndexedAverageCashFlow>& flow1,
95 const ext::shared_ptr<PriceTermStructure> priceCurve1,
96 const ext::shared_ptr<BlackVolTermStructure> vol1,
97 const ext::shared_ptr<CommodityIndexedAverageCashFlow>& flow2,
98 const ext::shared_ptr<PriceTermStructure> priceCurve2,
99 const ext::shared_ptr<BlackVolTermStructure> vol2, Real rho, Real strike,
100 Real df) {
101
102 QL_REQUIRE(flow1->startDate() == flow2->startDate() && flow1->endDate() == flow2->endDate(),
103 "Support only Averaging Flows with same observation Period");
104
105 Date today = Settings::instance().evaluationDate();
106
107 vector<double> timeGrid;
108 timeGrid.push_back(0.0);
109
110 double accrued1 = 0;
111 double accrued2 = 0;
112 size_t nObs = 0; // future obs
113 size_t n = flow1->indices().size();
114
115 for (const auto& [pricingDate, index] : flow1->indices()) {
116
117 Date fixingDate = index->fixingCalendar().adjust(pricingDate, Preceding);
118 if (pricingDate > today) {
119 timeGrid.push_back(vol1->timeFromReference(fixingDate));
120 nObs++;
121 } else {
122 accrued1 += index->fixing(fixingDate);
123 }
124 }
125
126 for (const auto& [pricingDate, index] : flow2->indices()) {
127 if (pricingDate <= today) {
128 Date fixingDate = index->fixingCalendar().adjust(pricingDate, Preceding);
129 accrued2 += index->fixing(fixingDate);
130 }
131 }
132
133 Array drifts;
134
135 constexpr size_t samples = 100000;
136 Matrix L(2, 2, 0);
137 L[0][0] = 1;
138 L[1][0] = rho;
139 L[1][1] = std::sqrt(1.0 - rho * rho);
140
141 Matrix drift(2, nObs, 0.0);
142 Matrix diffusion(2, nObs, 0.0);
143 Matrix St(2, nObs + 1, 0.0);
144
145 St[0][0] = std::log(priceCurve1->price(0));
146 St[1][0] = std::log(priceCurve2->price(0));
147
148 Matrix Z(2, nObs, 0.0);
149
150 for (size_t t = 0; t < nObs; ++t) {
151 drift[0][t] =
152 std::log(priceCurve1->price(timeGrid[t + 1]) / priceCurve1->price(timeGrid[t])) -
153 0.5 * vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
154 drift[1][t] =
155 std::log(priceCurve2->price(timeGrid[t + 1]) / priceCurve2->price(timeGrid[t])) -
156 0.5 * vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
157 diffusion[0][t] =
158 std::sqrt(vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
159 diffusion[1][t] =
160 std::sqrt(vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
161 }
162
163 double payoff = 0;
164 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(2 * nObs, 42);
165 for (size_t i = 0; i < samples; i++) {
166 double avg1 = 0;
167 double avg2 = 0;
168 auto sample = rsg.nextSequence().value;
169 std::copy(sample.begin(), sample.end(), Z.begin());
170 auto Zt = L * Z;
171 for (size_t t = 0; t < nObs; ++t) {
172 St[0][t + 1] = St[0][t] + drift[0][t] + diffusion[0][t] * Zt[0][t];
173 St[1][t + 1] = St[1][t] + drift[1][t] + diffusion[1][t] * Zt[1][t];
174 avg1 += std::exp(St[0][t + 1]);
175 avg2 += std::exp(St[1][t + 1]);
176 }
177 avg1 += accrued1;
178 avg2 += accrued2;
179 avg1 /= static_cast<double>(n);
180 avg2 /= static_cast<double>(n);
181
182 payoff += std::max(avg1 - avg2 - strike, 0.0);
183 }
184 payoff /= static_cast<double>(samples);
185 return df * payoff;
186}
187
188double monteCarloPricingFutureAveraging(const ext::shared_ptr<CommodityIndexedAverageCashFlow>& flow1,
189 const ext::shared_ptr<PriceTermStructure> priceCurve1,
190 const ext::shared_ptr<BlackVolTermStructure> vol1,
191 const ext::shared_ptr<CommodityIndexedAverageCashFlow>& flow2,
192 const ext::shared_ptr<PriceTermStructure> priceCurve2,
193 const ext::shared_ptr<BlackVolTermStructure> vol2, Real rho, Real strike,
194 Real df) {
195
196 QL_REQUIRE(flow1->startDate() == flow2->startDate() && flow1->endDate() == flow2->endDate(),
197 "Support only Averaging Flows with same observation Period");
198
199 Date futureExpiryDate = flow1->indices().begin()->second->expiryDate();
200 for (const auto& [p, ind] : flow1->indices()) {
201 QL_REQUIRE(ind->expiryDate() == futureExpiryDate, "MC pricer doesn't support future rolls in averaging");
202 }
203
204 futureExpiryDate = flow2->indices().begin()->second->expiryDate();
205 for (const auto& [p, ind] : flow2->indices()) {
206 QL_REQUIRE(ind->expiryDate() == futureExpiryDate, "MC pricer doesn't support future rolls in averaging");
207 }
208
209 vector<double> timeGrid;
210 timeGrid.push_back(0.0);
211 for (const auto& [pricingDate, index] : flow1->indices()) {
212 timeGrid.push_back(vol1->timeFromReference(pricingDate));
213 }
214
215 Array drifts;
216 size_t nObs = flow1->indices().size();
217 constexpr size_t samples = 100000;
218 Matrix L(2, 2, 0);
219 L[0][0] = 1;
220 L[1][0] = rho;
221 L[1][1] = std::sqrt(1.0 - rho * rho);
222
223 Matrix drift(2, nObs, 0.0);
224 Matrix diffusion(2, nObs, 0.0);
225 Matrix St(2, nObs + 1, 0.0);
226
227 auto& [p1, index1] = *flow1->indices().begin();
228 auto& [p2, index2] = *flow2->indices().begin();
229
230 St[0][0] = std::log(index1->fixing(p1));
231 St[1][0] = std::log(index2->fixing(p2));
232
233 Matrix Z(2, nObs, 0.0);
234
235 for (size_t t = 0; t < nObs; ++t) {
236 drift[0][t] =
237 -0.5 * vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
238 drift[1][t] =
239 -0.5 * vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
240 diffusion[0][t] =
241 std::sqrt(vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
242 diffusion[1][t] =
243 std::sqrt(vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
244 }
245
246 double payoff = 0;
247
248 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(2 * nObs, 42);
249 for (size_t i = 0; i < samples; i++) {
250 double avg1 = 0;
251 double avg2 = 0;
252 auto sample = rsg.nextSequence().value;
253 std::copy(sample.begin(), sample.end(), Z.begin());
254 auto Zt = L * Z;
255 for (size_t t = 0; t < nObs; ++t) {
256 St[0][t + 1] = St[0][t] + drift[0][t] + diffusion[0][t] * Zt[0][t];
257 St[1][t + 1] = St[1][t] + drift[1][t] + diffusion[1][t] * Zt[1][t];
258 avg1 += std::exp(St[0][t + 1]);
259 avg2 += std::exp(St[1][t + 1]);
260 }
261 avg1 /= static_cast<double>(nObs);
262 avg2 /= static_cast<double>(nObs);
263
264 payoff += std::max(avg1 - avg2 - strike, 0.0);
265 }
266 payoff /= static_cast<double>(samples);
267 return df * payoff;
268}
269
270} // namespace
271
272BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
273
274BOOST_AUTO_TEST_SUITE(CommoditySpreadOptionTests)
275
276BOOST_AUTO_TEST_CASE(testCrossAssetFutureSpread) {
277 Date today(5, Nov, 2022);
278 Settings::instance().evaluationDate() = today;
279
280 double strike = 1;
281 double volBrentQuote = 0.3;
282 double volWTIQuote = 0.35;
283 double quantity = 1000.;
284 double WTIspot = 100.;
285 double WTINov = 104.;
286 double WTIDec = 105.;
287 double brentSpot = 101;
288 double brentNov = 103;
289 double brentDec = 106;
290
291 Date novExpiry(30, Nov, 2022);
292 Date decExpiry(31, Dec, 2022);
293 Date exerciseDate(31, Dec, 2022);
294
295 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
296 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
297 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
298
299 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
300 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
301 auto wtiCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
302 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
303
304 auto discount = Handle<YieldTermStructure>(
305 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
306
307 auto brentVol = Handle<BlackVolTermStructure>(
308 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrentQuote, Actual365Fixed()));
309 auto wtiVol = Handle<BlackVolTermStructure>(
310 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volWTIQuote, Actual365Fixed()));
311
312 auto index1 = QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_USD", decExpiry, NullCalendar(), brentCurve);
313
314 auto index2 = QuantLib::ext::make_shared<CommodityFuturesIndex>("WTI_USD", decExpiry, NullCalendar(), wtiCurve);
315
316 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, decExpiry, decExpiry, index1);
317
318 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, decExpiry, decExpiry, index2);
319
320 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
321
322 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call);
323
324 for (const auto& rho : {-0.95, -0.5, -0.25, 0., 0.5, 0.75, 0.9, 0.95}) {
325 Handle<CorrelationTermStructure> corr = Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
326 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, brentVol, wtiVol, corr);
327 spreadOption.setPricingEngine(engine);
328 double npvMC = monteCarloPricing(brentDec, WTIDec, volBrentQuote, volWTIQuote, rho,
329 discount->timeFromReference(exercise->lastDate()),
330 discount->discount(exercise->lastDate()), strike) *
331 quantity;
332 double npvKrik = spreadOption.NPV();
333 BOOST_CHECK_CLOSE(npvKrik, npvMC, 1.);
334 }
335}
336
337BOOST_AUTO_TEST_CASE(testCalendarSpread) {
338 Date today(5, Nov, 2022);
339 Settings::instance().evaluationDate() = today;
340
341 double strike = 1;
342 double volBrent = 0.3;
343 double rho = 0.9;
344 double quantity = 1000.;
345 double spot = 100.;
346 double futureNov = 104.;
347 Date futureNovExpiry(30, Nov, 2022);
348 double futureDec = 105.;
349 Date futureDecExpiry(31, Dec, 2022);
350 Date exerciseDate(15, Nov, 2022);
351 Date paymentDate(17, Nov, 2022);
352
353 std::vector<Date> futureExpiryDates = {today, futureNovExpiry, futureDecExpiry};
354 std::vector<Real> brentQuotes = {spot, futureNov, futureDec};
355
356 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
357 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
358
359 auto discount = Handle<YieldTermStructure>(
360 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
361
362 auto vol1 = Handle<BlackVolTermStructure>(
363 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
364
365 auto index1 =
366 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
367
368 auto index2 =
369 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
370
371 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
372
373 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
374
375 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
376
377 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, paymentDate);
378
379 Handle<CorrelationTermStructure> corr =
380 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
381
382 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
383
384 spreadOption.setPricingEngine(engine);
385
386 double kirkNpv = spreadOption.NPV();
387 double mcNpv = quantity * monteCarloPricing(futureDec, futureNov, volBrent, volBrent, rho,
388 discount->timeFromReference(exercise->lastDate()),
389 discount->discount(paymentDate), strike);
390
391 BOOST_CHECK_CLOSE(kirkNpv, mcNpv, 1);
392}
393
394BOOST_AUTO_TEST_CASE(testCalendarSpread2) {
395 Date today(5, Nov, 2022);
396 Settings::instance().evaluationDate() = today;
397
398 double strike = 1;
399 double volBrent = 0.3;
400 double rho = 0.9;
401 double quantity = 1000.;
402 double spot = 100.;
403 double futureNov = 104.;
404 Date futureNovExpiry(30, Nov, 2022);
405 double futureDec = 105.;
406 Date futureDecExpiry(31, Dec, 2022);
407 Date exerciseDate(31, Dec, 2022);
408 Date paymentDate = exerciseDate;
409 std::vector<Date> futureExpiryDates = {today, futureNovExpiry, futureDecExpiry};
410 std::vector<Real> brentQuotes = {spot, futureNov, futureDec};
411
412 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
413 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
414
415 auto discount = Handle<YieldTermStructure>(
416 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
417
418 auto vol1 = Handle<BlackVolTermStructure>(
419 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
420
421 auto index1 =
422 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
423
424 auto index2 =
425 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
426
427 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
428
429 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
430
431 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
432
433 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, paymentDate);
434
435 Handle<CorrelationTermStructure> corr =
436 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
437
438 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
439
440 spreadOption.setPricingEngine(engine);
441 double kirkNpv = spreadOption.NPV();
442
443 double volScalingFactor = std::min(
444 std::sqrt(discount->timeFromReference(futureNovExpiry) / discount->timeFromReference(exercise->lastDate())),
445 1.0);
446
447 double mcNpv = quantity * monteCarloPricing(futureDec, futureNov, volBrent, volBrent * volScalingFactor, rho,
448 discount->timeFromReference(paymentDate),
449 discount->discount(exercise->lastDate()), strike);
450 BOOST_CHECK_CLOSE(kirkNpv, mcNpv, 1);
451}
452
453BOOST_AUTO_TEST_CASE(testCalendarSpreadEdgeCase) {
454 // The short asset price is already fixed
455 Date today(5, Dec, 2022);
456 Settings::instance().evaluationDate() = today;
457
458 double strike = 1;
459 double volBrent = 0.3;
460 double rho = 0.9;
461 double quantity = 1000.;
462 double spot = 100.;
463 double futureNov = 104.;
464 Date futureNovExpiry(30, Nov, 2022);
465 double futureDec = 105.;
466 Date futureDecExpiry(31, Dec, 2022);
467 Date exerciseDate(31, Dec, 2022);
468
469 std::vector<Date> futureExpiryDates = {today, futureDecExpiry};
470 std::vector<Real> brentQuotes = {spot, futureDec};
471
472 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
473 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
474
475 auto discount = Handle<YieldTermStructure>(
476 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
477
478 auto vol1 = Handle<BlackVolTermStructure>(
479 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
480
481 auto index1 =
482 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
483
484 auto index2 =
485 QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
486
487 index2->addFixing(futureNovExpiry, futureNov);
488
489 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
490
491 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
492
493 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
494
495 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, exerciseDate);
496
497 Handle<CorrelationTermStructure> corr =
498 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
499
500 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
501
502 spreadOption.setPricingEngine(engine);
503 double kirkNpv = spreadOption.NPV();
504
505 double bsNpv = quantity * blackFormula(QuantLib::Option::Call, strike + futureNov, futureDec,
506 std::sqrt(vol1->blackVariance(futureDecExpiry, strike + futureNov)),
507 discount->discount(exercise->lastDate()));
508
509 BOOST_CHECK_CLOSE(kirkNpv, bsNpv, 1e-8);
510}
511
512BOOST_AUTO_TEST_CASE(testSpotAveragingSpreadOption) {
513 Date today(31, Oct, 2022);
514 Settings::instance().evaluationDate() = today;
515
516 double strike = 1;
517 double volBrentQuote = 0.3;
518 double volWTIQuote = 0.35;
519 double quantity = 1000.;
520 double WTIspot = 100.;
521 double WTINov = 104.;
522 double WTIDec = 105.;
523 double brentSpot = 101;
524 double brentNov = 103;
525 double brentDec = 106;
526
527 Date novExpiry(30, Nov, 2022);
528 Date decExpiry(31, Dec, 2022);
529 Date exerciseDate(31, Dec, 2022);
530
531 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
532 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
533 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
534
535 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
536 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
537 auto wtiCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
538 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
539
540 auto discount = Handle<YieldTermStructure>(
541 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
542
543 auto brentVol = Handle<BlackVolTermStructure>(
544 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrentQuote, Actual365Fixed()));
545 auto wtiVol = Handle<BlackVolTermStructure>(
546 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volWTIQuote, Actual365Fixed()));
547
548 auto index1 = QuantLib::ext::make_shared<CommoditySpotIndex>("BRENT_USD", NullCalendar(), brentCurve);
549
550 auto index2 = QuantLib::ext::make_shared<CommoditySpotIndex>("WTI_USD", NullCalendar(), wtiCurve);
551
552 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
553 Date(31, Dec, 2022), index1, NullCalendar());
554
555 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
556 Date(31, Dec, 2022), index2, NullCalendar());
557
558 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
559
560 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call);
561
562 Real df = discount->discount(exercise->lastDate());
563
564 for (const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
565
566 Handle<CorrelationTermStructure> corr = Handle<CorrelationTermStructure>(
567 ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
568 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, brentVol, wtiVol, corr);
569 spreadOption.setPricingEngine(engine);
570 double npvMC = quantity * monteCarloPricingSpotAveraging(flow1, *brentCurve, *brentVol, flow2, *wtiCurve,
571 *wtiVol, rho, strike, df);
572 double npvKirk = spreadOption.NPV();
573 BOOST_CHECK_CLOSE(npvKirk, npvMC, 1);
574 }
575}
576
577BOOST_AUTO_TEST_CASE(testSeasonedSpotAveragingSpreadOption) {
578 Date today(10, Nov, 2022);
579 Settings::instance().evaluationDate() = today;
580
581 double strike = 1;
582 double volBrentQuote = 0.3;
583 double volWTIQuote = 0.35;
584 double quantity = 1000.;
585 double WTIspot = 100.;
586 double WTINov = 103.;
587 double WTIDec = 105.;
588 double brentSpot = 100;
589 double brentNov = 104;
590 double brentDec = 106;
591
592 Date novExpiry(30, Nov, 2022);
593 Date decExpiry(31, Dec, 2022);
594 Date exerciseDate(30, Nov, 2022);
595
596 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
597 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
598 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
599
600 vector<Date> fixingDates;
601
602 vector<Real> fixingValuesBrent;
603 vector<Real> fixingValuesWTI;
604
605 for (int i = 1; i <= 10; ++i) {
606 fixingDates.push_back(Date(i, Nov, 2022));
607 fixingValuesBrent.push_back(100 + i / 10.);
608 fixingValuesWTI.push_back(100 - i / 10.);
609 }
610
611 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
612 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
613 auto wtiCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
614 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
615
616 auto discount = Handle<YieldTermStructure>(
617 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
618
619 auto brentVol = Handle<BlackVolTermStructure>(
620 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrentQuote, Actual365Fixed()));
621 auto wtiVol = Handle<BlackVolTermStructure>(
622 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volWTIQuote, Actual365Fixed()));
623
624 auto index1 = QuantLib::ext::make_shared<CommoditySpotIndex>("BRENT_USD", NullCalendar(), brentCurve);
625
626 auto index2 = QuantLib::ext::make_shared<CommoditySpotIndex>("WTI_USD", NullCalendar(), wtiCurve);
627
628 index1->addFixings(fixingDates.begin(), fixingDates.end(), fixingValuesBrent.begin());
629 index2->addFixings(fixingDates.begin(), fixingDates.end(), fixingValuesWTI.begin());
630
631 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Nov, 2022), Date(30, Nov, 2022),
632 Date(30, Nov, 2022), index1, NullCalendar());
633
634 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Nov, 2022), Date(30, Nov, 2022),
635 Date(30, Nov, 2022), index2, NullCalendar());
636
637 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
638
639 Real df = discount->discount(exercise->lastDate());
640
641 for (const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
642 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call);
643
644 Handle<CorrelationTermStructure> corr = Handle<CorrelationTermStructure>(
645 ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
646 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, brentVol, wtiVol, corr);
647 spreadOption.setPricingEngine(engine);
648 double npvMC = quantity * monteCarloPricingSpotAveraging(flow1, *brentCurve, *brentVol, flow2, *wtiCurve,
649 *wtiVol, rho, strike, df);
650 double npvKirk = spreadOption.NPV();
651 BOOST_CHECK_CLOSE(npvKirk, npvMC, 1);
652
653 }
654
655 for (const auto& rho : {0.85}) {
656 for (const auto& strike : {0.5, 1., 1.5, 2.0, 2.5}) {
657 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call);
658
659 Handle<CorrelationTermStructure> corr = Handle<CorrelationTermStructure>(
660 ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
661 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, brentVol, wtiVol, corr);
662 spreadOption.setPricingEngine(engine);
663 double npvMC = quantity * monteCarloPricingSpotAveraging(flow1, *brentCurve, *brentVol, flow2, *wtiCurve,
664 *wtiVol, rho, strike, df);
665 double npvKirk = spreadOption.NPV();
666 BOOST_CHECK_CLOSE(npvKirk, npvMC, 1);
667 }
668 }
669}
670
671BOOST_AUTO_TEST_CASE(testFutureAveragingSpreadOption) {
672 Date today(31, Oct, 2022);
673 Settings::instance().evaluationDate() = today;
674
675 double strike = 1;
676 double volBrentQuote = 0.3;
677 double volWTIQuote = 0.35;
678 double quantity = 1000.;
679 double WTIspot = 100.;
680 double WTINov = 104.;
681 double WTIDec = 105.;
682 double brentSpot = 101;
683 double brentNov = 103;
684 double brentDec = 106;
685
686 Date novExpiry(30, Nov, 2022);
687 Date decExpiry(31, Dec, 2022);
688 Date exerciseDate(31, Dec, 2022);
689
690 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
691 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
692 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
693
694 auto feCalc = ext::make_shared<MockUpExpiryCalculator>();
695
696 auto brentCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
697 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
698 auto wtiCurve = Handle<PriceTermStructure>(QuantLib::ext::make_shared<InterpolatedPriceCurve<Linear>>(
699 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
700
701 auto discount = Handle<YieldTermStructure>(
702 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
703
704 auto brentVol = Handle<BlackVolTermStructure>(
705 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrentQuote, Actual365Fixed()));
706 auto wtiVol = Handle<BlackVolTermStructure>(
707 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volWTIQuote, Actual365Fixed()));
708
709 auto index1 = QuantLib::ext::make_shared<CommodityFuturesIndex>("BRENT_USD", novExpiry, NullCalendar(), brentCurve);
710
711 auto index2 = QuantLib::ext::make_shared<CommodityFuturesIndex>("WTI_USD", novExpiry, NullCalendar(), wtiCurve);
712
713 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
714 Date(31, Dec, 2022), index1, NullCalendar(), 0.0,
715 1.0, true, 0, 0, feCalc);
716
717 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
718 Date(31, Dec, 2022), index2, NullCalendar(), 0.0,
719 1.0, true, 0, 0, feCalc);
720
721 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
722
723 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call);
724
725 Real df = discount->discount(exercise->lastDate());
726
727 for (const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
728
729 Handle<CorrelationTermStructure> corr = Handle<CorrelationTermStructure>(
730 ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
731 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, brentVol, wtiVol, corr);
732 spreadOption.setPricingEngine(engine);
733 double npvMC = quantity * monteCarloPricingFutureAveraging(flow1, *brentCurve, *brentVol, flow2, *wtiCurve,
734 *wtiVol, rho, strike, df);
735 double npvKirk = spreadOption.NPV();
736 BOOST_CHECK_CLOSE(npvKirk, npvMC, 1);
737 }
738}
739
740
741BOOST_AUTO_TEST_SUITE_END()
742
743BOOST_AUTO_TEST_SUITE_END()
Base class for classes that perform date calculations for future contracts.
virtual QuantLib::Date contractDate(const QuantLib::Date &expiryDate)=0
virtual QuantLib::Date priorExpiry(bool includeExpiry=true, const QuantLib::Date &referenceDate=QuantLib::Date(), bool forOption=false)=0
virtual QuantLib::Date nextExpiry(bool includeExpiry=true, const QuantLib::Date &referenceDate=QuantLib::Date(), QuantLib::Natural offset=0, bool forOption=false)=0
virtual QuantLib::Date expiryDate(const QuantLib::Date &contractDate, QuantLib::Natural monthOffset=0, bool forOption=false)=0
virtual QuantLib::Date applyFutureMonthOffset(const QuantLib::Date &contractDate, Natural futureMonthOffset)=0
Interpolated price curve.
Definition: pricecurve.hpp:50
Cash flow dependent on a single commodity spot price or future's settlement price.
commodity spread option engine
Term structure of flat correlations.
Base class for classes that perform date calculations for future contracts.
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Definition: inflation.cpp:183
Interpolated price curve.
BOOST_AUTO_TEST_CASE(testCrossAssetFutureSpread)
Fixture that can be used at top level.