Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commoditylegbuilder.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
29#include <ql/cashflows/fixedratecoupon.hpp>
30#include <ql/cashflows/simplecashflow.hpp>
31#include <ql/time/calendars/weekendsonly.hpp>
32#include <ql/time/daycounters/one.hpp>
36
37using namespace ore::data;
38using namespace QuantExt;
39using namespace QuantLib;
40
41using std::make_pair;
42using std::string;
43
44namespace {
45
46// Utility method used below to update the non-averaging leg quantities if necessary.
47// Note that the non-averaging leg may be representing an averaging leg by referring to a commodity price curve
48// that gives the prices of averaging futures.
49void updateQuantities(Leg& leg, bool isAveragingFuture, CommodityQuantityFrequency cqf, const Schedule& schedule,
50 bool excludePeriodStart, bool includePeriodEnd,
51 const QuantLib::ext::shared_ptr<CommodityFutureConvention>& conv,
52 const QuantLib::ext::shared_ptr<FutureExpiryCalculator>& calc, Natural hoursPerDay, bool useBusinessDays,
53 const std::string& daylightSavingLocation, const string& commName, bool unrealisedQuantity,
54 const boost::optional<pair<Calendar, Real>>& offPeakPowerData) {
55
56 QL_REQUIRE(leg.size() == schedule.size() - 1, "The number of schedule periods (" << (schedule.size() - 1) <<
57 ") was expected to equal the number of leg cashflows (" << leg.size() << ") when updating quantities" <<
58 " for commodity " << commName << ".");
59
61 if (cqf == CQF::PerCalculationPeriod) {
62
63 if (unrealisedQuantity) {
64 if (!isAveragingFuture) {
65 DLOG("The future " << commName << " is not averaging, unrealisedQuantity does not make sense" <<
66 " so the PerCalculationPeriod quantities have not been altered.");
67 } else if (conv->contractFrequency() == Daily) {
68 DLOG("The future " << commName << " is averaging but has a daily frequency " <<
69 " so the PerCalculationPeriod quantities have not been altered.");
70 } else {
71 // If unrealisedQuantity is true, find a cashflow where today in [start, end) and update.
72 QL_REQUIRE(calc, "Updating commodity quantities due to unrealisedQuantity = true, expected a" <<
73 " valid future expiry calculator, commodity is " << commName << ".");
74 Size numberCashflows = leg.size();
75 for (Size i = 0; i < numberCashflows; ++i) {
76
77 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(leg[i]);
78 QL_REQUIRE(ccf, io::ordinal(i + 1) << " quantity for commodity " << commName <<
79 ", expected a valid CommodityIndexedCashFlow.");
80 Date pricingDate = ccf->pricingDate();
81 Date aveEnd = calc->priorExpiry(true, pricingDate);
82 Date aveStart = calc->priorExpiry(false, aveEnd);
83 Date today = Settings::instance().evaluationDate();
84
85 if (aveStart <= today && today < aveEnd) {
86
87 QL_REQUIRE(conv, "Need a valid convention for " << commName <<
88 " while updating quantities due to unrealisedQuantity = true.");
89 auto pds = pricingDates(aveStart, aveEnd, conv->calendar(), true, true, useBusinessDays);
90
91 DLOG("UnrealisedQuantity is true so updating the quantity:");
92 DLOG("today: " << io::iso_date(today));
93 DLOG("pricing date: " << io::iso_date(pricingDate));
94 DLOG("period start: " << io::iso_date(aveStart));
95 DLOG("period end: " << io::iso_date(aveEnd));
96
97 Real unrealisedFraction = 0.0;
98 if (offPeakPowerData) {
99
100 Calendar peakCalendar = offPeakPowerData->first;
101 Real offPeakHours = offPeakPowerData->second;
102 Real total = 0;
103 Real unrealised = 0;
104 for (const auto& pd : pds) {
105 Real numHours = peakCalendar.isHoliday(pd) ? 24.0 : offPeakHours;
106 total += numHours;
107 if (pd > today)
108 unrealised += numHours;
109 }
110 DLOG("total hours: " << total);
111 DLOG("unrealised hours: " << unrealised);
112 unrealisedFraction = unrealised / total;
113
114 } else {
115
116 auto unrealised = count_if(pds.begin(), pds.end(),
117 [&today](const Date& pd) { return pd > today; });
118 DLOG("total pricing dates: " << pds.size());
119 DLOG("unrealised pricing dates: " << unrealised);
120 unrealisedFraction = static_cast<Real>(unrealised) / pds.size();
121
122 }
123
124 if (unrealisedFraction > 0) {
125 Real oldQuantity = ccf->quantity();
126 Real newQuantity = oldQuantity / unrealisedFraction;
127 DLOG("old quantity: " << oldQuantity);
128 DLOG("new quantity: " << newQuantity);
129 ccf->setPeriodQuantity(newQuantity);
130 } else {
131 DLOG("UnrealisedQuantity is true but cannot update the quantity because" <<
132 " value of unrealised is 0.");
133 }
134
135 // There will only be one cashflow satisfying the condition
136 break;
137 }
138 }
139 }
140 } else {
141 DLOG("updateQuantities: quantity is PerCalculationPeriod and unrealisedQuantity is" <<
142 " false so nothing to update.");
143 }
144
145 } else if (cqf == CQF::PerCalendarDay) {
146
147 QL_REQUIRE(conv, "Need a valid convention for " << commName <<
148 " while updating quantities (PerCalendarDay).");
149
150 DLOG("updateQuantities: updating quantities based on PerCalendarDay.");
151
152 Size numberCashflows = leg.size();
153 for (Size i = 0; i < numberCashflows; ++i) {
154 Date start = schedule.date(i);
155 Date end = schedule.date(i + 1);
156 bool excludeStart = i == 0 ? false : excludePeriodStart;
157 bool includeEnd = i == numberCashflows - 1 ? true : includePeriodEnd;
158 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(leg[i]);
159 QL_REQUIRE(ccf, "Updating " << io::ordinal(i + 1) << " quantity for commodity " << commName <<
160 ", expected a valid CommodityIndexedCashFlow.");
161 Real newQuantity = ccf->quantity() * ((end - start - 1.0) +
162 (!excludeStart ? 1.0 : 0.0) + (includeEnd ? 1.0 : 0.0));
163 ccf->setPeriodQuantity(newQuantity);
164 DLOG("updateQuantities: updating quantity for pricing date " << ccf->pricingDate() << " from " <<
165 ccf->quantity() << " to " << newQuantity);
166 }
167
168 } else {
169
170 // Now, cqf is either PerHour, PerHourAndCalendarDay or PerPricingDay
171 QL_REQUIRE(cqf == CQF::PerPricingDay || cqf == CQF::PerHour || cqf == CQF::PerHourAndCalendarDay,
172 "Did not cover commodity"
173 << " quantity frequency type " << cqf << " while updating quantities for " << commName << ".");
174
175 // Store the original quantities and CommodityIndexedCashFlow pointers
176 Size numberCashflows = leg.size();
177 vector<Real> quantities(numberCashflows, 0.0);
178 vector<QuantLib::ext::shared_ptr<CommodityIndexedCashFlow>> ccfs(numberCashflows);
179 for (Size i = 0; i < numberCashflows; ++i) {
180 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(leg[i]);
181 QL_REQUIRE(ccf, "Updating " << io::ordinal(i + 1) << " quantity for commodity " << commName <<
182 ", expected a valid CommodityIndexedCashFlow.");
183 ccfs[i] = ccf;
184 quantities[i] = ccf->quantity();
185 }
186
187 if (!isAveragingFuture) {
188
189 if (cqf == CQF::PerPricingDay) {
190 DLOG("The future " << commName << " is not averaging so a commodity quantity frequency equal to" <<
191 " PerPricingDay does not make sense. Quantities have not been altered. Commodity is " <<
192 commName << ".");
193 } else {
194 DLOG("updateQuantities: the future " << commName << " is not averaging and quantity frequency is" <<
195 " PerHour so updating quantities with daily quantities.");
196 if (offPeakPowerData) {
197 QL_REQUIRE(cqf == CQF::PerHour, "PerHourAndCalendarDay not allowed for "
198 "off-peak power contracts, expected PerHour");
199 Calendar peakCalendar = offPeakPowerData->first;
200 Real offPeakHours = offPeakPowerData->second;
201 for (Size i = 0; i < numberCashflows; ++i) {
202 Real numHours = peakCalendar.isHoliday(ccfs[i]->pricingDate()) ? 24.0 : offPeakHours;
203 ccfs[i]->setPeriodQuantity(quantities[i] * numHours);
204 DLOG("updateQuantities: updating quantity for pricing date " << ccfs[i]->pricingDate() <<
205 " from " << quantities[i] << " to " << (quantities[i] * numHours));
206 }
207 } else {
208 QL_REQUIRE(hoursPerDay != Null<Natural>(),
209 "Need HoursPerDay when commodity quantity frequency"
210 << " is PerHour or PerHourAndCalendarDay. Updating quantities failed, commodity is "
211 << commName << ".");
212 for (Size i = 0; i < numberCashflows; ++i) {
213 Real newQuantity = 0.0;
214 if (cqf == CQF::PerHour) {
215 newQuantity = quantities[i] * hoursPerDay;
216 } else if (cqf == CQF::PerHourAndCalendarDay) {
217 Date start = schedule.date(i);
218 Date end = schedule.date(i + 1);
219 bool excludeStart = i == 0 ? false : excludePeriodStart;
220 bool includeEnd = i == numberCashflows - 1 ? true : includePeriodEnd;
221 int numberOfDays = ((end - start - 1) + (!excludeStart ? 1 : 0) + (includeEnd ? 1 : 0));
222 newQuantity =
223 quantities[i] * static_cast<Real>(hoursPerDay) * static_cast<Real>(numberOfDays) +
224 static_cast<Real>(daylightSavingCorrection(daylightSavingLocation, start, end));
225 }
226 ccfs[i]->setPeriodQuantity(newQuantity);
227 DLOG("updateQuantities: updating quantity for pricing date "
228 << ccfs[i]->pricingDate() << " from " << quantities[i] << " to "
229 << (quantities[i] * hoursPerDay));
230 }
231 }
232 }
233
234 } else {
235
236 QL_REQUIRE(conv, "Need a valid convention for " << commName <<
237 " while updating quantities for averaging future (PerPricingDay/PerHour).");
238
239 // Averaging future and cqf is either PerHour or PerPricingDay.
240 // Need to calculate associated averaging period and the number of pricing dates in the period.
241 if (conv->contractFrequency() == Daily) {
242 DLOG("The future " << commName << " is averaging but has a daily frequency " <<
243 " so the quantities have not been altered.");
244 } else {
245
246 // Frequency must be monthly or greater. We loop over each period in the schedule, imply
247 // the associated averaging period using the future expiry calculator and determine the per
248 // calculation period quantities in each calculation period. We assume that the averaging period
249 // goes from expiry to expiry.
250 QL_REQUIRE(calc, "Updating commodity quantities expected a valid future expiry calculator" <<
251 ", commodity is " << commName << ".");
252 DLOG("The future " << commName << " is averaging and does not have a daily frequency.");
253 for (Size i = 0; i < numberCashflows; ++i) {
254 Date pricingDate = ccfs[i]->pricingDate();
255 Date aveEnd = calc->priorExpiry(true, pricingDate);
256 Date aveStart = calc->priorExpiry(false, aveEnd);
257 auto pds = pricingDates(aveStart, aveEnd, conv->calendar(), true, true, useBusinessDays);
258 if (cqf == CQF::PerHour || cqf == CQF::PerHourAndCalendarDay) {
259 if (offPeakPowerData) {
260 QL_REQUIRE(cqf == CQF::PerHour, "PerHourAndCalendarDay not allowed for "
261 "off-peak power contracts, expected PerHour");
262 Calendar peakCalendar = offPeakPowerData->first;
263 Real offPeakHours = offPeakPowerData->second;
264 Real newQuantity = 0.0;
265 for (const auto& pd : pds) {
266 newQuantity += quantities[i] * (peakCalendar.isHoliday(pd) ? 24.0 : offPeakHours);
267 }
268 ccfs[i]->setPeriodQuantity(newQuantity);
269 DLOG("updateQuantities: updating quantity for pricing date " << ccfs[i]->pricingDate() <<
270 " from " << quantities[i] << " to " << newQuantity);
271 } else {
272 QL_REQUIRE(hoursPerDay != Null<Natural>(),
273 "Need HoursPerDay when commodity"
274 << " quantity frequency is PerHour or PerHourAndCalendarDay. Commodity is "
275 << commName << ".");
276 Real newQuantity = 0.0;
277 if(cqf == CQF::PerHour) {
278 newQuantity = quantities[i] * hoursPerDay * pds.size();
279 } else if (cqf == CQF::PerHourAndCalendarDay) {
280 Date start = schedule.date(i);
281 Date end = schedule.date(i + 1);
282 bool excludeStart = i == 0 ? false : excludePeriodStart;
283 bool includeEnd = i == numberCashflows - 1 ? true : includePeriodEnd;
284 int numberOfDays =
285 ((end - start - 1) + (!excludeStart ? 1 : 0) + (includeEnd ? 1 : 0));
286 newQuantity =
287 quantities[i] *
288 (static_cast<Real>(hoursPerDay) * static_cast<Real>(numberOfDays) +
289 static_cast<Real>(daylightSavingCorrection(daylightSavingLocation, start, end)));
290 }
291 ccfs[i]->setPeriodQuantity(newQuantity);
292 DLOG("updateQuantities: updating quantity for pricing date " << ccfs[i]->pricingDate() <<
293 " from " << quantities[i] << " to " << newQuantity);
294 }
295 } else {
296 ccfs[i]->setPeriodQuantity(quantities[i] * pds.size());
297 DLOG("updateQuantities: updating quantity for pricing date " << ccfs[i]->pricingDate() <<
298 " from " << quantities[i] << " to " << (quantities[i] * pds.size()));
299 }
300 }
301
302 }
303 }
304 }
305}
306}
307
308namespace ore {
309namespace data {
310
311Leg CommodityFixedLegBuilder::buildLeg(const LegData& data, const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory,
312 RequiredFixings& requiredFixings, const string& configuration,
313 const QuantLib::Date& openEndDateReplacement, const bool useXbsCurves) const {
314
315 // Check that our leg data has commodity fixed leg data
316 auto fixedLegData = QuantLib::ext::dynamic_pointer_cast<CommodityFixedLegData>(data.concreteLegData());
317 QL_REQUIRE(fixedLegData, "Wrong LegType, expected CommodityFixed, got " << data.legType());
318
319 // Build our schedule and get the quantities and prices
320 Schedule schedule = makeSchedule(data.schedule(),openEndDateReplacement);
321 vector<Real> prices = buildScheduledVector(fixedLegData->prices(), fixedLegData->priceDates(), schedule);
322 vector<Real> quantities = buildScheduledVector(fixedLegData->quantities(), fixedLegData->quantityDates(), schedule);
323
324 // Build fixed rate leg with 1/1 day counter, prices as rates and quantities as notionals so that we have price x
325 // notional in each period as the amount. We don't make any payment date adjustments yet as they come later.
326 OneDayCounter dc;
327 Leg fixedRateLeg = FixedRateLeg(schedule)
328 .withNotionals(quantities)
329 .withCouponRates(prices, dc)
330 .withPaymentAdjustment(Unadjusted)
331 .withPaymentLag(0)
332 .withPaymentCalendar(NullCalendar());
333
334 // Get explicit payment dates which in most cases should be empty
335 vector<Date> paymentDates;
336 if (!data.paymentDates().empty()) {
337 paymentDates = parseVectorOfValues<Date>(data.paymentDates(), &parseDate);
338 if (fixedLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::FutureExpiryDate) {
339 QL_REQUIRE(paymentDates.size() == fixedRateLeg.size(),
340 "Expected the number of payment dates derived from float leg with tag '"
341 << fixedLegData->tag() << "' (" << paymentDates.size()
342 << ") to equal the number of fixed price periods (" << fixedRateLeg.size()
343 << "). Are the leg schedules consistent? Should CommodityPayRelativeTo = FutureExpiryDate "
344 "be used?");
345 } else {
346 QL_REQUIRE(paymentDates.size() == fixedRateLeg.size(),
347 "Expected the number of explicit payment dates ("
348 << paymentDates.size() << ") to equal the number of fixed price periods ("
349 << fixedRateLeg.size() << ")");
350 }
351 }
352
353 // Create the commodity fixed leg.
354 Leg commodityFixedLeg;
355 for (Size i = 0; i < fixedRateLeg.size(); i++) {
356
357 auto cp = QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(fixedRateLeg[i]);
358
359 // Get payment date
360 Date pmtDate;
361 if (!paymentDates.empty()) {
362 // Explicit payment dates were provided
363 Calendar paymentCalendar =
364 data.paymentCalendar().empty() ? NullCalendar() : parseCalendar(data.paymentCalendar());
365 PaymentLag paymentLag = parsePaymentLag(data.paymentLag());
366 Period paymentLagPeriod = boost::apply_visitor(PaymentLagPeriod(), paymentLag);
367 BusinessDayConvention paymentConvention =
368 data.paymentConvention().empty() ? Unadjusted : parseBusinessDayConvention(data.paymentConvention());
369 pmtDate = paymentCalendar.advance(paymentDates[i], paymentLagPeriod, paymentConvention);
370 } else {
371 // Gather the payment conventions.
372 BusinessDayConvention bdc =
373 data.paymentConvention().empty() ? Following : parseBusinessDayConvention(data.paymentConvention());
374
375 Calendar paymentCalendar =
376 data.paymentCalendar().empty() ? schedule.calendar() : parseCalendar(data.paymentCalendar());
377
378 PaymentLag payLag = parsePaymentLag(data.paymentLag());
379 Period paymentLag = boost::apply_visitor(PaymentLagPeriod(), payLag);
380
381 // Get unadjusted payment date based on pay relative to value.
382 if (fixedLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodEndDate) {
383 pmtDate = cp->accrualEndDate();
384 } else if (fixedLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodStartDate) {
385 pmtDate = cp->accrualStartDate();
386 } else if (fixedLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::TerminationDate) {
387 pmtDate = fixedRateLeg.back()->date();
388 } else if (fixedLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::FutureExpiryDate) {
389 QL_FAIL("Internal error: commodity fixed leg builder can not determine payment date relative to future "
390 "expiry date, this has to be handled in the instrument builder.");
391 } else {
392 QL_FAIL("Unexpected value " << fixedLegData->commodityPayRelativeTo() << " for CommodityPayRelativeTo");
393 }
394
395 // Adjust the payment date using the payment conventions
396 pmtDate = paymentCalendar.advance(pmtDate, paymentLag, bdc);
397 }
398
399 // Create the fixed cashflow for this period
400 commodityFixedLeg.push_back(QuantLib::ext::make_shared<SimpleCashFlow>(cp->amount(), pmtDate));
401 }
402
403 applyIndexing(commodityFixedLeg, data, engineFactory, requiredFixings, openEndDateReplacement, useXbsCurves);
404 addToRequiredFixings(commodityFixedLeg, QuantLib::ext::make_shared<FixingDateGetter>(requiredFixings));
405
406 addToRequiredFixings(commodityFixedLeg, QuantLib::ext::make_shared<ore::data::FixingDateGetter>(requiredFixings));
407
408 return commodityFixedLeg;
409}
410
411Leg CommodityFloatingLegBuilder::buildLeg(const LegData& data, const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory,
412 RequiredFixings& requiredFixings, const string& configuration,
413 const QuantLib::Date& openEndDateReplacement, const bool useXbsCurves) const {
414
415 // allAveraging_ flag should be reset to false before each build. If we do not do this, the allAveraging_
416 // flag may have been set from building a different leg previously
417 allAveraging_ = false;
418
419 auto floatingLegData = QuantLib::ext::dynamic_pointer_cast<CommodityFloatingLegData>(data.concreteLegData());
420 QL_REQUIRE(floatingLegData, "Wrong LegType: expected CommodityFloating but got " << data.legType());
421
422 // Commodity name and its conventions.
423 // Default weekends only calendar used to create the "index". Attempt to populate with sth sensible here.
424 string commName = floatingLegData->name();
425 Calendar commCal = WeekendsOnly();
426 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
427 QuantLib::ext::shared_ptr<CommodityFutureConvention> commFutureConv;
428 boost::optional<pair<Calendar, Real>> offPeakPowerData;
429 bool balanceOfTheMonth = false;
430 if (conventions->has(commName)) {
431 QuantLib::ext::shared_ptr<Convention> commConv = conventions->get(commName);
432
433 // If commodity forward convention
434 if (auto c = QuantLib::ext::dynamic_pointer_cast<CommodityForwardConvention>(commConv)) {
435 if (c->advanceCalendar() != NullCalendar()) {
436 commCal = c->advanceCalendar();
437 }
438 }
439
440 // If commodity future convention
441 commFutureConv = QuantLib::ext::dynamic_pointer_cast<CommodityFutureConvention>(commConv);
442 if (commFutureConv) {
443 balanceOfTheMonth = commFutureConv->balanceOfTheMonth();
444 commCal = commFutureConv->calendar();
445 if (const auto& oppid = commFutureConv->offPeakPowerIndexData()) {
446 offPeakPowerData = make_pair(oppid->peakCalendar(), oppid->offPeakHours());
447 }
448 }
449 }
450
451 // Get price type i.e. is the commodity floating leg referencing the spot price or a future settlement price.
452 CommodityPriceType priceType = floatingLegData->priceType();
453
454 // If referencing a future settlement price, we will need a valid FutureExpiryCalculator below.
455 QuantLib::ext::shared_ptr<FutureExpiryCalculator> feCalc;
456 if (priceType == CommodityPriceType::FutureSettlement) {
457
458 // We should have a valid commodity future convention in this case but check anyway
459 QL_REQUIRE(commFutureConv, "Expected to have a commodity future convention for commodity " << commName);
460
461 feCalc = QuantLib::ext::make_shared<ConventionsBasedFutureExpiry>(*commFutureConv);
462
463 // If the future contract is averaging but the trade is not averaging, we can't price the trade
464 // TODO: Should we just issue a warning here, switch the trade to averaging and price it?
465 if (commFutureConv->isAveraging()) {
466 QL_REQUIRE(floatingLegData->isAveraged(),
467 "The future, " << commName << ", is averaging but the leg is not.");
468 allAveraging_ = true;
469 }
470 }
471
472 // Construct the commodity index.
473 Handle<PriceTermStructure> priceCurve = engineFactory->market()->commodityPriceCurve(commName, configuration);
474 auto index = parseCommodityIndex(commName, false, priceCurve, NullCalendar(),
476
477 // Get the commodity floating leg schedule and quantities
478 Schedule schedule = makeSchedule(data.schedule());
479 vector<Real> quantities =
480 buildScheduledVector(floatingLegData->quantities(), floatingLegData->quantityDates(), schedule);
481
482 // Get spreads and gearings which may be empty
483 vector<Real> spreads = buildScheduledVector(floatingLegData->spreads(), floatingLegData->spreadDates(), schedule);
484 vector<Real> gearings =
485 buildScheduledVector(floatingLegData->gearings(), floatingLegData->gearingDates(), schedule);
486
487 // Get explicit pricing dates which in most cases should be empty
488 vector<Date> pricingDates;
489 if (!floatingLegData->pricingDates().empty()) {
490 pricingDates = parseVectorOfValues<Date>(floatingLegData->pricingDates(), &parseDate);
491 }
492
493 // Some common variables needed in building the commodity floating leg
494 PaymentLag paymentLag = parsePaymentLag(data.paymentLag());
495 BusinessDayConvention paymentConvention =
496 data.paymentConvention().empty() ? Following : parseBusinessDayConvention(data.paymentConvention());
497 Calendar paymentCalendar =
498 data.paymentCalendar().empty() ? schedule.calendar() : parseCalendar(data.paymentCalendar());
499 Calendar pricingCalendar;
500
501 // Override missing pricing calendar with calendar from convention
502 if (floatingLegData->pricingCalendar().empty() && floatingLegData->isAveraged() && balanceOfTheMonth &&
503 commFutureConv) {
504 pricingCalendar = commFutureConv->balanceOfTheMonthPricingCalendar();
505 } else if (floatingLegData->pricingCalendar().empty()) {
506 pricingCalendar = commCal;
507 } else {
508 pricingCalendar = parseCalendar(floatingLegData->pricingCalendar());
509 }
510
511 // Get explicit payment dates which in most cases should be empty
512 vector<Date> paymentDates;
513
514
515 Schedule paymentSchedule = makeSchedule(data.paymentSchedule(), openEndDateReplacement);
516
517 if (!paymentSchedule.empty()) {
518 paymentDates = paymentSchedule.dates();
519 } else if (!data.paymentDates().empty()) {
520 BusinessDayConvention paymentDatesConvention =
521 data.paymentConvention().empty() ? Unadjusted : parseBusinessDayConvention(data.paymentConvention());
522 Calendar paymentDatesCalendar =
523 data.paymentCalendar().empty() ? NullCalendar() : parseCalendar(data.paymentCalendar());
524 paymentDates = parseVectorOfValues<Date>(data.paymentDates(), &parseDate);
525 for (Size i = 0; i < paymentDates.size(); i++)
526 paymentDates[i] = paymentDatesCalendar.adjust(paymentDates[i], paymentDatesConvention);
527 }
528
529 // May need to poulate hours per day
530 auto hoursPerDay = floatingLegData->hoursPerDay();
531 if ((floatingLegData->commodityQuantityFrequency() == CommodityQuantityFrequency::PerHour ||
532 floatingLegData->commodityQuantityFrequency() == CommodityQuantityFrequency::PerHourAndCalendarDay) &&
533 hoursPerDay == Null<Natural>()) {
534 QL_REQUIRE(commFutureConv,
535 "Commodity floating leg commodity frequency set to PerHour / PerHourAndCalendarDay but"
536 << " no HoursPerDay provided in floating leg data and no commodity future convention for "
537 << commName);
538 hoursPerDay = commFutureConv->hoursPerDay();
539 QL_REQUIRE(commFutureConv,
540 "Commodity floating leg commodity frequency set to PerHour / PerHourAndCalendarDay but"
541 << " no HoursPerDay provided in floating leg data and commodity future convention for "
542 << commName << " does not provide it.");
543 }
544
545 // populate daylight saving begin / end
546 std::string daylightSavingLocation;
547 if (floatingLegData->commodityQuantityFrequency() == CommodityQuantityFrequency::PerHourAndCalendarDay) {
548 QL_REQUIRE(
549 commFutureConv,
550 "Commodity floating leg commodity frequency set to PerHourAndCalendarDay, need commodity convention for "
551 << commName);
552 daylightSavingLocation = commFutureConv->savingsTime();
553 }
554 QuantLib::ext::shared_ptr<FxIndex> fxIndex;
555 // Build the leg. Different ctor depending on whether cashflow is averaging or not.
556 Leg leg;
557
558 bool isCashFlowAveraged =
559 floatingLegData->isAveraged() && !allAveraging_ && floatingLegData->lastNDays() == Null<Natural>();
560
561 // If daily expiry offset is given, check that referenced future contract has a daily frequency.
562 auto dailyExpOffset = floatingLegData->dailyExpiryOffset();
563 if (dailyExpOffset != Null<Natural>() && dailyExpOffset > 0) {
564 QL_REQUIRE(commFutureConv, "A positive DailyExpiryOffset has been provided but no commodity"
565 << " future convention given for " << commName);
566 QL_REQUIRE(commFutureConv->contractFrequency() == Daily,
567 "A positive DailyExpiryOffset has been"
568 << " provided but the commodity contract frequency is not Daily ("
569 << commFutureConv->contractFrequency() << ")");
570 }
571
572 if (!floatingLegData->fxIndex().empty()) { // build the fxIndex for daily average conversion
573 auto underlyingCcy = priceCurve->currency().code();
574 auto npvCurrency = data.currency();
575 if (underlyingCcy != npvCurrency) // only need an FX Index if currencies differ
576 fxIndex = buildFxIndex(floatingLegData->fxIndex(), npvCurrency, underlyingCcy, engineFactory->market(),
577 engineFactory->configuration(MarketContext::pricing));
578 }
579
580 if (isCashFlowAveraged) {
582 CommodityIndexedAverageCashFlow::PaymentTiming::InArrears;
583 if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodStartDate) {
584 paymentTiming = CommodityIndexedAverageCashFlow::PaymentTiming::InAdvance;
585 } else if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodEndDate) {
586 paymentTiming = CommodityIndexedAverageCashFlow::PaymentTiming::InArrears;
587 } else if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::FutureExpiryDate) {
588 QL_FAIL("CommodityLegBuilder: CommodityPayRelativeTo 'FutureExpiryDate' not allowed for average cashflow.");
589 } else {
590 QL_FAIL("CommodityLegBuilder: CommodityPayRelativeTo " << floatingLegData->commodityPayRelativeTo()
591 << " not handled. This is an internal error.");
592 }
593
594 leg = CommodityIndexedAverageLeg(schedule, index)
595 .withQuantities(quantities)
596 .withPaymentLag(boost::apply_visitor(PaymentLagInteger(), paymentLag))
597 .withPaymentCalendar(paymentCalendar)
598 .withPaymentConvention(paymentConvention)
599 .withPricingCalendar(pricingCalendar)
600 .withSpreads(spreads)
601 .withGearings(gearings)
602 .paymentTiming(paymentTiming)
604 .withDeliveryDateRoll(floatingLegData->deliveryRollDays())
605 .withFutureMonthOffset(floatingLegData->futureMonthOffset())
607 .payAtMaturity(floatingLegData->commodityPayRelativeTo() ==
609 .includeEndDate(floatingLegData->includePeriodEnd())
610 .excludeStartDate(floatingLegData->excludePeriodStart())
611 .withQuantityFrequency(floatingLegData->commodityQuantityFrequency())
612 .withPaymentDates(paymentDates)
613 .useBusinessDays(floatingLegData->useBusinessDays())
614 .withHoursPerDay(hoursPerDay)
615 .withDailyExpiryOffset(dailyExpOffset)
616 .unrealisedQuantity(floatingLegData->unrealisedQuantity())
617 .withOffPeakPowerData(offPeakPowerData)
618 .withFxIndex(fxIndex);
619 } else {
620 CommodityIndexedCashFlow::PaymentTiming paymentTiming = CommodityIndexedCashFlow::PaymentTiming::InArrears;
621 if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodStartDate) {
622 paymentTiming = CommodityIndexedCashFlow::PaymentTiming::InAdvance;
623 } else if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::CalculationPeriodEndDate) {
624 paymentTiming = CommodityIndexedCashFlow::PaymentTiming::InArrears;
625 } else if (floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::FutureExpiryDate) {
626 paymentTiming = CommodityIndexedCashFlow::PaymentTiming::RelativeToExpiry;
627 } else {
628 QL_FAIL("CommodityLegBuilder: CommodityPayRelativeTo " << floatingLegData->commodityPayRelativeTo()
629 << " not handled. This is an internal error.");
630 }
631
632 leg = CommodityIndexedLeg(schedule, index)
633 .withQuantities(quantities)
634 .withPaymentLag(boost::apply_visitor(PaymentLagInteger(), paymentLag))
635 .withPaymentCalendar(paymentCalendar)
636 .withPaymentConvention(paymentConvention)
637 .withPricingLag(floatingLegData->pricingLag())
638 .withPricingLagCalendar(pricingCalendar)
639 .withSpreads(spreads)
640 .withGearings(gearings)
641 .paymentTiming(paymentTiming)
642 .inArrears(floatingLegData->isInArrears())
644 .useFutureExpiryDate(floatingLegData->pricingDateRule() == CommodityPricingDateRule::FutureExpiryDate)
645 .withFutureMonthOffset(floatingLegData->futureMonthOffset())
647 .payAtMaturity(floatingLegData->commodityPayRelativeTo() == CommodityPayRelativeTo::TerminationDate)
649 .withPaymentDates(paymentDates)
650 .withDailyExpiryOffset(dailyExpOffset)
651 .withFxIndex(fxIndex)
652 .withIsAveraging(floatingLegData->isAveraged() && balanceOfTheMonth)
653 .withPricingCalendar(pricingCalendar)
654 .includeEndDate(floatingLegData->includePeriodEnd())
655 .excludeStartDate(floatingLegData->excludePeriodStart());
656
657 // Possibly update the leg's quantities.
658 updateQuantities(leg, allAveraging_, floatingLegData->commodityQuantityFrequency(), schedule,
659 floatingLegData->excludePeriodStart(), floatingLegData->includePeriodEnd(), commFutureConv,
660 feCalc, hoursPerDay, floatingLegData->useBusinessDays(), daylightSavingLocation, commName,
661 floatingLegData->unrealisedQuantity(), offPeakPowerData);
662
663 // If lastNDays is set, amend each cashflow in the leg to an averaging cashflow over the lastNDays.
664 auto lastNDays = floatingLegData->lastNDays();
665 if (lastNDays != Null<Natural>() && lastNDays > 1) {
666 if (commFutureConv) {
667 if (lastNDays > 31) {
668 WLOG("LastNDays (" << lastNDays << ") should not be greater than 31. " <<
669 "Proceed as if it is not set.");
670 } else if (commFutureConv->isAveraging()) {
671 WLOG("Commodity future convention for " << commName << " is averaging so LastNDays (" <<
672 lastNDays << ") is ignored. Proceed as if it is not set.");
673 } else {
674 DLOG("Amending cashflows to account for LastNDays (" << lastNDays << ").");
675 const auto& cal = commFutureConv->calendar();
676 for (auto& cf : leg) {
677 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf);
678 const Date& endDate = ccf->pricingDate();
679 Date startDate = cal.advance(endDate, -static_cast<Integer>(lastNDays) + 1, Days, Preceding);
680 TLOG("Creating cashflow averaging over period [" << io::iso_date(startDate) <<
681 "," << io::iso_date(endDate) << "]");
682 cf = QuantLib::ext::make_shared<CommodityIndexedAverageCashFlow>(ccf->periodQuantity(), startDate,
683 endDate, ccf->date(), ccf->index(), cal, ccf->spread(), ccf->gearing(),
684 ccf->useFuturePrice(), 0, 0, feCalc, true, false);
685 }
686 }
687 } else {
688 WLOG("Need a commodity future convention for " << commName << " when LastNDays (" << lastNDays <<
689 ") is set and greater than 1. Proceed as if it is not set.");
690 }
691 }
692 }
693
694 if (fxIndex) {
695 // fx daily indexing needed
696 for (auto cf : leg) {
697 auto cacf = QuantLib::ext::dynamic_pointer_cast<CommodityCashFlow>(cf);
698 QL_REQUIRE(cacf, "Commodity Indexed averaged cashflow is required to compute daily converted average.");
699 for (auto kv : cacf->indices()) {
700 if (!fxIndex->fixingCalendar().isBusinessDay(
701 kv.first)) {
702 /* If fx index is not available for the commodity pricing day, this ensures to require the previous
703 * valid one which will be used in pricing from fxIndex()->fixing(...) */
704 Date adjustedFixingDate = fxIndex->fixingCalendar().adjust(kv.first, Preceding);
705 requiredFixings.addFixingDate(adjustedFixingDate, floatingLegData->fxIndex());
706 } else
707 requiredFixings.addFixingDate(kv.first, floatingLegData->fxIndex());
708 }
709 }
710 } else {
711 // standard indexing approach
712 applyIndexing(leg, data, engineFactory, requiredFixings, openEndDateReplacement, useXbsCurves);
713 }
714
715 addToRequiredFixings(leg, QuantLib::ext::make_shared<FixingDateGetter>(requiredFixings));
716 return leg;
717}
718} // namespace data
719} // namespace ore
Engine builder for commodity swaps.
CommodityIndexedAverageLeg & useFuturePrice(bool flag=false)
CommodityIndexedAverageLeg & withPaymentLag(QuantLib::Natural paymentLag)
CommodityIndexedAverageLeg & includeEndDate(bool flag=true)
CommodityIndexedAverageLeg & excludeStartDate(bool flag=true)
CommodityIndexedAverageLeg & withFutureMonthOffset(QuantLib::Natural futureMonthOffset)
CommodityIndexedAverageLeg & withGearings(QuantLib::Real gearing)
CommodityIndexedAverageLeg & paymentTiming(CommodityIndexedAverageCashFlow::PaymentTiming paymentTiming)
CommodityIndexedAverageLeg & withDeliveryDateRoll(QuantLib::Natural deliveryDateRoll)
CommodityIndexedAverageLeg & withQuantityFrequency(CommodityQuantityFrequency quantityFrequency)
CommodityIndexedAverageLeg & withPaymentConvention(QuantLib::BusinessDayConvention paymentConvention)
CommodityIndexedAverageLeg & withPaymentDates(const std::vector< QuantLib::Date > &paymentDates)
CommodityIndexedAverageLeg & withDailyExpiryOffset(QuantLib::Natural dailyExpiryOffset)
CommodityIndexedAverageLeg & withQuantities(QuantLib::Real quantity)
CommodityIndexedAverageLeg & withPaymentCalendar(const QuantLib::Calendar &paymentCalendar)
CommodityIndexedAverageLeg & withHoursPerDay(QuantLib::Natural hoursPerDay)
CommodityIndexedAverageLeg & useBusinessDays(bool flag=true)
CommodityIndexedAverageLeg & withFutureExpiryCalculator(const ext::shared_ptr< FutureExpiryCalculator > &calc=nullptr)
CommodityIndexedAverageLeg & withPricingCalendar(const QuantLib::Calendar &pricingCalendar)
CommodityIndexedAverageLeg & withSpreads(QuantLib::Real spread)
CommodityIndexedAverageLeg & unrealisedQuantity(bool flag=false)
CommodityIndexedAverageLeg & withOffPeakPowerData(const boost::optional< std::pair< QuantLib::Calendar, QuantLib::Real > > &offPeakPowerData)
CommodityIndexedAverageLeg & withFxIndex(const ext::shared_ptr< FxIndex > &fxIndex)
CommodityIndexedAverageLeg & payAtMaturity(bool flag=false)
CommodityIndexedLeg & excludeStartDate(bool flag=true)
CommodityIndexedLeg & withPricingLagCalendar(const QuantLib::Calendar &pricingLagCalendar)
CommodityIndexedLeg & paymentTiming(CommodityIndexedCashFlow::PaymentTiming paymentTiming)
CommodityIndexedLeg & payAtMaturity(bool flag=false)
CommodityIndexedLeg & withIsAveraging(const bool isAveraging)
CommodityIndexedLeg & includeEndDate(bool flag=true)
CommodityIndexedLeg & withFutureMonthOffset(QuantLib::Natural futureMonthOffset)
CommodityIndexedLeg & withQuantities(QuantLib::Real quantity)
CommodityIndexedLeg & withPaymentDates(const std::vector< QuantLib::Date > &paymentDates)
CommodityIndexedLeg & withFxIndex(const ext::shared_ptr< FxIndex > &fxIndex)
CommodityIndexedLeg & withFutureExpiryCalculator(const ext::shared_ptr< FutureExpiryCalculator > &calc=nullptr)
CommodityIndexedLeg & withPaymentLag(QuantLib::Natural paymentLag)
CommodityIndexedLeg & inArrears(bool flag=true)
CommodityIndexedLeg & withGearings(QuantLib::Real gearing)
CommodityIndexedLeg & withDailyExpiryOffset(QuantLib::Natural dailyExpiryOffset)
CommodityIndexedLeg & withPaymentCalendar(const QuantLib::Calendar &paymentCalendar)
CommodityIndexedLeg & withPricingDates(const std::vector< QuantLib::Date > &pricingDates)
CommodityIndexedLeg & withSpreads(QuantLib::Real spread)
CommodityIndexedLeg & useFuturePrice(bool flag=false)
CommodityIndexedLeg & withPricingCalendar(const QuantLib::Calendar &pricingCalendar)
CommodityIndexedLeg & withPaymentConvention(QuantLib::BusinessDayConvention paymentConvention)
CommodityIndexedLeg & withPricingLag(QuantLib::Natural pricingLag)
CommodityIndexedLeg & useFutureExpiryDate(bool flag=true)
QuantLib::Leg buildLeg(const ore::data::LegData &data, const QuantLib::ext::shared_ptr< ore::data::EngineFactory > &engineFactory, RequiredFixings &requiredFixings, const std::string &configuration, const QuantLib::Date &openEndDateReplacement=Null< Date >(), const bool useXbsCurves=false) const override
QuantLib::Leg buildLeg(const ore::data::LegData &data, const QuantLib::ext::shared_ptr< ore::data::EngineFactory > &engineFactory, RequiredFixings &requiredFixings, const std::string &configuration, const QuantLib::Date &openEndDateReplacement=Null< Date >(), const bool useXbsCurves=false) const override
Serializable object holding leg data.
Definition: legdata.hpp:844
void addFixingDate(const QuantLib::Date &fixingDate, const std::string &indexName, const QuantLib::Date &payDate=Date::maxDate(), const bool alwaysAddIfPaysOnSettlement=false, const bool mandatoryFixing=true)
Commodity fixed and floating leg builders.
leg data for commodity leg types
Base class for classes that perform date calculations for future contracts.
Logic for calculating required fixing dates on legs.
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Definition: parsers.cpp:157
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
Definition: parsers.cpp:173
PaymentLag parsePaymentLag(const string &s)
Convert text to PaymentLag.
Definition: parsers.cpp:628
Map text representations to QuantLib/QuantExt types.
leg data model and serialization
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
market data related utilties
CommodityQuantityFrequency
QuantLib::Integer daylightSavingCorrection(const std::string &location, const QuantLib::Date &start, const QuantLib::Date &end)
set< Date > pricingDates(const Date &s, const Date &e, const Calendar &pricingCalendar, bool excludeStart, bool includeEnd, bool useBusinessDays)
void applyIndexing(Leg &leg, const LegData &data, const QuantLib::ext::shared_ptr< EngineFactory > &engineFactory, RequiredFixings &requiredFixings, const QuantLib::Date &openEndDateReplacement, const bool useXbsCurves)
Definition: legdata.cpp:2633
void addToRequiredFixings(const QuantLib::Leg &leg, const QuantLib::ext::shared_ptr< FixingDateGetter > &fixingDateGetter)
vector< T > buildScheduledVector(const vector< T > &values, const vector< string > &dates, const Schedule &schedule, const bool checkAllValuesAppearInResult=false)
Definition: legdata.hpp:1061
QuantLib::ext::shared_ptr< QuantExt::CommodityIndex > parseCommodityIndex(const string &name, bool hasPrefix, const Handle< PriceTermStructure > &ts, const Calendar &cal, const bool enforceFutureIndex)
boost::variant< QuantLib::Period, QuantLib::Natural > PaymentLag
Definition: types.hpp:32
QuantLib::ext::shared_ptr< QuantExt::FxIndex > buildFxIndex(const string &fxIndex, const string &domestic, const string &foreign, const QuantLib::ext::shared_ptr< Market > &market, const string &configuration, bool useXbsCurves)
Definition: marketdata.cpp:137
Schedule makeSchedule(const ScheduleDates &data)
Definition: schedule.cpp:263
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.