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>
37using namespace boost::unit_test_framework;
46 virtual QuantLib::Date
nextExpiry(
bool includeExpiry,
const QuantLib::Date& referenceDate, QuantLib::Natural offset,
47 bool forOption)
override {
48 return Date::endOfMonth(referenceDate);
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;
54 virtual QuantLib::Date
expiryDate(
const QuantLib::Date& contractDate, QuantLib::Natural monthOffset,
55 bool forOption)
override {
56 return Date::endOfMonth(contractDate);
60 Natural futureMonthOffset)
override {
61 return QuantLib::Date();
65double monteCarloPricing(
double F1,
double F2,
double sigma1,
double sigma2,
double rho,
double ttm,
double df,
67 constexpr size_t seed = 42;
68 constexpr size_t samples = 100000;
71 L[1][0] = rho * sigma2;
72 L[1][1] = std::sqrt(1.0 - rho * rho) * sigma2;
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);
91 return payoff * df / (double)samples;
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,
102 QL_REQUIRE(flow1->startDate() == flow2->startDate() && flow1->endDate() == flow2->endDate(),
103 "Support only Averaging Flows with same observation Period");
105 Date today = Settings::instance().evaluationDate();
107 vector<double> timeGrid;
108 timeGrid.push_back(0.0);
113 size_t n = flow1->indices().size();
115 for (
const auto& [pricingDate, index] : flow1->indices()) {
117 Date
fixingDate = index->fixingCalendar().adjust(pricingDate, Preceding);
118 if (pricingDate > today) {
119 timeGrid.push_back(vol1->timeFromReference(fixingDate));
122 accrued1 += index->fixing(fixingDate);
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);
135 constexpr size_t samples = 100000;
139 L[1][1] = std::sqrt(1.0 - rho * rho);
141 Matrix drift(2, nObs, 0.0);
142 Matrix diffusion(2, nObs, 0.0);
143 Matrix St(2, nObs + 1, 0.0);
145 St[0][0] = std::log(priceCurve1->price(0));
146 St[1][0] = std::log(priceCurve2->price(0));
148 Matrix Z(2, nObs, 0.0);
150 for (
size_t t = 0; t < nObs; ++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]));
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]));
158 std::sqrt(vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
160 std::sqrt(vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
164 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(2 * nObs, 42);
165 for (
size_t i = 0; i < samples; i++) {
168 auto sample = rsg.nextSequence().value;
169 std::copy(sample.begin(), sample.end(), Z.begin());
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]);
179 avg1 /=
static_cast<double>(n);
180 avg2 /=
static_cast<double>(n);
182 payoff += std::max(avg1 - avg2 - strike, 0.0);
184 payoff /=
static_cast<double>(samples);
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,
196 QL_REQUIRE(flow1->startDate() == flow2->startDate() && flow1->endDate() == flow2->endDate(),
197 "Support only Averaging Flows with same observation Period");
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");
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");
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));
216 size_t nObs = flow1->indices().size();
217 constexpr size_t samples = 100000;
221 L[1][1] = std::sqrt(1.0 - rho * rho);
223 Matrix drift(2, nObs, 0.0);
224 Matrix diffusion(2, nObs, 0.0);
225 Matrix St(2, nObs + 1, 0.0);
227 auto& [p1, index1] = *flow1->indices().begin();
228 auto& [p2, index2] = *flow2->indices().begin();
230 St[0][0] = std::log(index1->fixing(p1));
231 St[1][0] = std::log(index2->fixing(p2));
233 Matrix Z(2, nObs, 0.0);
235 for (
size_t t = 0; t < nObs; ++t) {
237 -0.5 * vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
239 -0.5 * vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1]));
241 std::sqrt(vol1->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
243 std::sqrt(vol2->blackForwardVariance(timeGrid[t], timeGrid[t + 1], priceCurve1->price(timeGrid[t + 1])));
248 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(2 * nObs, 42);
249 for (
size_t i = 0; i < samples; i++) {
252 auto sample = rsg.nextSequence().value;
253 std::copy(sample.begin(), sample.end(), Z.begin());
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]);
261 avg1 /=
static_cast<double>(nObs);
262 avg2 /=
static_cast<double>(nObs);
264 payoff += std::max(avg1 - avg2 - strike, 0.0);
266 payoff /=
static_cast<double>(samples);
274BOOST_AUTO_TEST_SUITE(CommoditySpreadOptionTests)
277 Date today(5, Nov, 2022);
278 Settings::instance().evaluationDate() = today;
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;
291 Date novExpiry(30, Nov, 2022);
292 Date decExpiry(31, Dec, 2022);
293 Date exerciseDate(31, Dec, 2022);
295 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
296 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
297 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
300 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
302 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
304 auto discount = Handle<YieldTermStructure>(
305 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
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()));
312 auto index1 = QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_USD", decExpiry, NullCalendar(), brentCurve);
314 auto index2 = QuantLib::ext::make_shared<CommodityFuturesIndex>(
"WTI_USD", decExpiry, NullCalendar(), wtiCurve);
316 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, decExpiry, decExpiry, index1);
318 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, decExpiry, decExpiry, index2);
320 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
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) *
332 double npvKrik = spreadOption.NPV();
333 BOOST_CHECK_CLOSE(npvKrik, npvMC, 1.);
338 Date today(5, Nov, 2022);
339 Settings::instance().evaluationDate() = today;
342 double volBrent = 0.3;
344 double quantity = 1000.;
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);
353 std::vector<Date> futureExpiryDates = {today, futureNovExpiry, futureDecExpiry};
354 std::vector<Real> brentQuotes = {spot, futureNov, futureDec};
357 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
359 auto discount = Handle<YieldTermStructure>(
360 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
362 auto vol1 = Handle<BlackVolTermStructure>(
363 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
366 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
369 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
371 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
373 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
375 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
377 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, paymentDate);
379 Handle<CorrelationTermStructure> corr =
380 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
382 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
384 spreadOption.setPricingEngine(engine);
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);
391 BOOST_CHECK_CLOSE(kirkNpv, mcNpv, 1);
395 Date today(5, Nov, 2022);
396 Settings::instance().evaluationDate() = today;
399 double volBrent = 0.3;
401 double quantity = 1000.;
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};
413 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
415 auto discount = Handle<YieldTermStructure>(
416 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
418 auto vol1 = Handle<BlackVolTermStructure>(
419 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
422 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
425 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
427 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
429 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
431 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
433 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, paymentDate);
435 Handle<CorrelationTermStructure> corr =
436 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
438 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
440 spreadOption.setPricingEngine(engine);
441 double kirkNpv = spreadOption.NPV();
443 double volScalingFactor = std::min(
444 std::sqrt(discount->timeFromReference(futureNovExpiry) / discount->timeFromReference(exercise->lastDate())),
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);
455 Date today(5, Dec, 2022);
456 Settings::instance().evaluationDate() = today;
459 double volBrent = 0.3;
461 double quantity = 1000.;
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);
469 std::vector<Date> futureExpiryDates = {today, futureDecExpiry};
470 std::vector<Real> brentQuotes = {spot, futureDec};
473 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
475 auto discount = Handle<YieldTermStructure>(
476 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
478 auto vol1 = Handle<BlackVolTermStructure>(
479 QuantLib::ext::make_shared<BlackConstantVol>(today, NullCalendar(), volBrent, Actual365Fixed()));
482 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_DEC_USD", futureDecExpiry, NullCalendar(), brentCurve);
485 QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_NOV_USD", futureNovExpiry, NullCalendar(), brentCurve);
487 index2->addFixing(futureNovExpiry, futureNov);
489 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureDecExpiry, Date(31, Dec, 2022), index1);
491 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedCashFlow>(100, futureNovExpiry, Date(30, Nov, 2022), index2);
493 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
495 CommoditySpreadOption spreadOption(flow1, flow2, exercise, quantity, strike, Option::Call, exerciseDate);
497 Handle<CorrelationTermStructure> corr =
498 Handle<CorrelationTermStructure>(ext::make_shared<FlatCorrelation>(0, NullCalendar(), rho, Actual365Fixed()));
500 auto engine = QuantLib::ext::make_shared<CommoditySpreadOptionAnalyticalEngine>(discount, vol1, vol1, corr);
502 spreadOption.setPricingEngine(engine);
503 double kirkNpv = spreadOption.NPV();
505 double bsNpv = quantity * blackFormula(QuantLib::Option::Call, strike + futureNov, futureDec,
506 std::sqrt(vol1->blackVariance(futureDecExpiry, strike + futureNov)),
507 discount->discount(exercise->lastDate()));
509 BOOST_CHECK_CLOSE(kirkNpv, bsNpv, 1e-8);
513 Date today(31, Oct, 2022);
514 Settings::instance().evaluationDate() = today;
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;
527 Date novExpiry(30, Nov, 2022);
528 Date decExpiry(31, Dec, 2022);
529 Date exerciseDate(31, Dec, 2022);
531 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
532 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
533 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
536 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
538 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
540 auto discount = Handle<YieldTermStructure>(
541 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
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()));
548 auto index1 = QuantLib::ext::make_shared<CommoditySpotIndex>(
"BRENT_USD", NullCalendar(), brentCurve);
550 auto index2 = QuantLib::ext::make_shared<CommoditySpotIndex>(
"WTI_USD", NullCalendar(), wtiCurve);
552 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
553 Date(31, Dec, 2022), index1, NullCalendar());
555 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Dec, 2022), Date(31, Dec, 2022),
556 Date(31, Dec, 2022), index2, NullCalendar());
558 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
562 Real df = discount->discount(exercise->lastDate());
564 for (
const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
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);
578 Date today(10, Nov, 2022);
579 Settings::instance().evaluationDate() = today;
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;
592 Date novExpiry(30, Nov, 2022);
593 Date decExpiry(31, Dec, 2022);
594 Date exerciseDate(30, Nov, 2022);
596 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
597 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
598 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
600 vector<Date> fixingDates;
602 vector<Real> fixingValuesBrent;
603 vector<Real> fixingValuesWTI;
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.);
612 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
614 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
616 auto discount = Handle<YieldTermStructure>(
617 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
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()));
624 auto index1 = QuantLib::ext::make_shared<CommoditySpotIndex>(
"BRENT_USD", NullCalendar(), brentCurve);
626 auto index2 = QuantLib::ext::make_shared<CommoditySpotIndex>(
"WTI_USD", NullCalendar(), wtiCurve);
628 index1->addFixings(fixingDates.begin(), fixingDates.end(), fixingValuesBrent.begin());
629 index2->addFixings(fixingDates.begin(), fixingDates.end(), fixingValuesWTI.begin());
631 auto flow1 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Nov, 2022), Date(30, Nov, 2022),
632 Date(30, Nov, 2022), index1, NullCalendar());
634 auto flow2 = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(quantity, Date(1, Nov, 2022), Date(30, Nov, 2022),
635 Date(30, Nov, 2022), index2, NullCalendar());
637 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
639 Real df = discount->discount(exercise->lastDate());
641 for (
const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
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);
655 for (
const auto& rho : {0.85}) {
656 for (
const auto& strike : {0.5, 1., 1.5, 2.0, 2.5}) {
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);
672 Date today(31, Oct, 2022);
673 Settings::instance().evaluationDate() = today;
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;
686 Date novExpiry(30, Nov, 2022);
687 Date decExpiry(31, Dec, 2022);
688 Date exerciseDate(31, Dec, 2022);
690 std::vector<Date> futureExpiryDates = {today, novExpiry, decExpiry};
691 std::vector<Real> brentQuotes = {brentSpot, brentNov, brentDec};
692 std::vector<Real> wtiQuotes = {WTIspot, WTINov, WTIDec};
694 auto feCalc = ext::make_shared<MockUpExpiryCalculator>();
697 today, futureExpiryDates, brentQuotes, Actual365Fixed(), USDCurrency()));
699 today, futureExpiryDates, wtiQuotes, Actual365Fixed(), USDCurrency()));
701 auto discount = Handle<YieldTermStructure>(
702 QuantLib::ext::make_shared<FlatForward>(today, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
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()));
709 auto index1 = QuantLib::ext::make_shared<CommodityFuturesIndex>(
"BRENT_USD", novExpiry, NullCalendar(), brentCurve);
711 auto index2 = QuantLib::ext::make_shared<CommodityFuturesIndex>(
"WTI_USD", novExpiry, NullCalendar(), wtiCurve);
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);
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);
721 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(exerciseDate);
725 Real df = discount->discount(exercise->lastDate());
727 for (
const auto& rho : {-0.9, -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 0.9}) {
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);
741BOOST_AUTO_TEST_SUITE_END()
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.
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)
Interpolated price curve.
BOOST_AUTO_TEST_CASE(testCrossAssetFutureSpread)
Fixture that can be used at top level.