Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commodityindexedaveragecashflow.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
20#include <ql/utilities/vectors.hpp>
21#include <numeric>
22
23using QuantLib::AcyclicVisitor;
24using QuantLib::BusinessDayConvention;
25using QuantLib::Calendar;
26using QuantLib::CashFlow;
27using QuantLib::Date;
28using QuantLib::Integer;
29using QuantLib::MakeSchedule;
30using QuantLib::Real;
31using QuantLib::Schedule;
32using QuantLib::Visitor;
33using QuantLib::io::iso_date;
34using std::accumulate;
35using std::count_if;
36using std::pair;
37using std::vector;
38
39namespace QuantExt {
40
42 Real quantity, const Date& startDate, const Date& endDate, const Date& paymentDate,
43 const ext::shared_ptr<CommodityIndex>& index, const Calendar& pricingCalendar, QuantLib::Real spread,
44 QuantLib::Real gearing, bool useFuturePrice, Natural deliveryDateRoll, Natural futureMonthOffset,
45 const ext::shared_ptr<FutureExpiryCalculator>& calc, bool includeEndDate, bool excludeStartDate,
46 bool useBusinessDays, CommodityQuantityFrequency quantityFrequency, Natural hoursPerDay, Natural dailyExpiryOffset,
47 bool unrealisedQuantity, const boost::optional<pair<Calendar, Real>>& offPeakPowerData,
48 const ext::shared_ptr<FxIndex>& fxIndex)
49 : CommodityCashFlow(quantity, spread, gearing, useFuturePrice, index, fxIndex), startDate_(startDate),
50 endDate_(endDate),
51 paymentDate_(paymentDate), pricingCalendar_(pricingCalendar), deliveryDateRoll_(deliveryDateRoll),
52 futureMonthOffset_(futureMonthOffset), includeEndDate_(includeEndDate), excludeStartDate_(excludeStartDate),
53 useBusinessDays_(useBusinessDays), quantityFrequency_(quantityFrequency), hoursPerDay_(hoursPerDay),
54 dailyExpiryOffset_(dailyExpiryOffset), unrealisedQuantity_(unrealisedQuantity),
55 offPeakPowerData_(offPeakPowerData) {
56 init(calc);
57}
58
59CommodityIndexedAverageCashFlow::CommodityIndexedAverageCashFlow(
60 Real quantity, const Date& startDate, const Date& endDate, Natural paymentLag, Calendar paymentCalendar,
61 BusinessDayConvention paymentConvention, const ext::shared_ptr<CommodityIndex>& index,
62 const Calendar& pricingCalendar, QuantLib::Real spread, QuantLib::Real gearing, PaymentTiming paymentTiming,
63 bool useFuturePrice, Natural deliveryDateRoll, Natural futureMonthOffset,
64 const ext::shared_ptr<FutureExpiryCalculator>& calc, bool includeEndDate, bool excludeStartDate,
65 const QuantLib::Date& paymentDateOverride, bool useBusinessDays, CommodityQuantityFrequency quantityFrequency,
66 Natural hoursPerDay, Natural dailyExpiryOffset, bool unrealisedQuantity,
67 const boost::optional<pair<Calendar, Real>>& offPeakPowerData, const ext::shared_ptr<FxIndex>& fxIndex)
68 : CommodityCashFlow(quantity, spread, gearing, useFuturePrice, index, fxIndex), startDate_(startDate), endDate_(endDate),
69 paymentDate_(paymentDateOverride), pricingCalendar_(pricingCalendar),
70 deliveryDateRoll_(deliveryDateRoll), futureMonthOffset_(futureMonthOffset), includeEndDate_(includeEndDate),
71 excludeStartDate_(excludeStartDate), useBusinessDays_(useBusinessDays), quantityFrequency_(quantityFrequency),
72 hoursPerDay_(hoursPerDay), dailyExpiryOffset_(dailyExpiryOffset), unrealisedQuantity_(unrealisedQuantity),
73 offPeakPowerData_(offPeakPowerData) {
74
75 // Derive the payment date
76 if (paymentDate_ == Date()) {
77 paymentDate_ = paymentTiming == PaymentTiming::InArrears ? endDate : startDate;
78 paymentDate_ = paymentCalendar.advance(endDate, paymentLag, Days, paymentConvention);
79 }
80
81 init(calc);
82}
83
84void CommodityIndexedAverageCashFlow::performCalculations() const {
85
86 // Calculate the average price
87 averagePrice_ = 0.0;
88 Real fxRate = 0.0;
89 if (weights_.empty()) {
90 for (const auto& kv : indices_) {
91 fxRate = (fxIndex_)? this->fxIndex()->fixing(kv.first):1.0;
92 averagePrice_ += fxRate * kv.second->fixing(kv.first);
93 }
94 averagePrice_ /= indices_.size();
95 } else {
96 // weights_ will be populated when offPeakPowerData_ is provided.
97 for (const auto& kv : indices_) {
98 fxRate = (fxIndex_)? this->fxIndex()->fixing(kv.first):1.0;
99 averagePrice_ += fxRate * kv.second->fixing(kv.first) * weights_.at(kv.first);
100 }
101 }
102
103 // Amount is just average price times quantity
104 // In case of Foreign currency settlement, the spread must be expressed in Foreign currency units
105 amount_ = periodQuantity_ * (gearing_ * averagePrice_ + spread_);
106}
107
108Real CommodityIndexedAverageCashFlow::amount() const {
109 calculate();
110 return amount_;
111}
112
113Real CommodityIndexedAverageCashFlow::fixing() const {
114 calculate();
115 return averagePrice_;
116}
117
118void CommodityIndexedAverageCashFlow::accept(AcyclicVisitor& v) {
119 if (Visitor<CommodityIndexedAverageCashFlow>* v1 = dynamic_cast<Visitor<CommodityIndexedAverageCashFlow>*>(&v))
120 v1->visit(*this);
121 else
122 CommodityCashFlow::accept(v);
123}
124
125void CommodityIndexedAverageCashFlow::init(const ext::shared_ptr<FutureExpiryCalculator>& calc) {
126
127 // If pricing calendar is not set, use the index fixing calendar
128 if (pricingCalendar_ == Calendar()) {
129 pricingCalendar_ = index_->fixingCalendar();
130 }
131
132 // If we are going to reference a future settlement price, check that we have a valid expiry calculator
133 if (useFuturePrice_) {
134 QL_REQUIRE(calc, "CommodityIndexedCashFlow needs a valid future expiry calculator when using first future");
135 }
136
137 // Store the relevant index for each pricing date taking account of the flags and the pricing calendar
138 auto pds = pricingDates(startDate_, endDate_, pricingCalendar_,
139 excludeStartDate_, includeEndDate_, useBusinessDays_);
140
141 QL_REQUIRE(!pds.empty(), "CommodityIndexedAverageCashFlow: found no pricing dates between "
142 << io::iso_date(startDate_) << " and " << io::iso_date(endDate_) << ".");
143
144 // Populate the indices_ map with the correct values.
145 if (!useFuturePrice_) {
146
147 // If not using future prices, just observe spot on every pricing date.
148 for (const Date& pd : pds) {
149 indices_.push_back({pd,index_});
150 }
151
152 } else {
153
154 // If using future prices, first fill indices assuming delivery date roll is 0.
155 for (const Date& pd : pds) {
156 auto expiry = calc->nextExpiry(true, pd, futureMonthOffset_);
157
158 // If given an offset for each expiry, apply it now.
159 if (dailyExpiryOffset_ != Null<Natural>()) {
160 expiry = index_->fixingCalendar().advance(expiry, dailyExpiryOffset_ * Days);
161 }
162
163 indices_.push_back({pd, index_->clone(expiry)});
164 }
165
166 // Update indices_ where necessary if delivery date roll is greater than 0.
167 if (deliveryDateRoll_ > 0) {
168
169 Date expiry;
170 Date prevExpiry;
171 Date rollDate;
172
173 for (auto& kv : indices_) {
174
175 // If expiry is different from previous expiry, update the roll date.
176 expiry = kv.second->expiryDate();
177 if (expiry != prevExpiry) {
178 rollDate = pricingCalendar_.advance(expiry,
179 -static_cast<Integer>(deliveryDateRoll_), Days);
180 }
181 prevExpiry = expiry;
182
183 // If the pricing date is after the roll date, we use the next expiry.
184 if (kv.first > rollDate) {
185 expiry = calc->nextExpiry(false, expiry);
186 kv.second = index_->clone(expiry);
187 }
188
189 }
190 }
191 }
192
193 // Register with each of the indices.
194 for (auto& kv : indices_) {
195 registerWith(kv.second);
196 }
197
198 // If offPeakPowerData_ is provided, populate weights_
199 if (offPeakPowerData_) {
200 Calendar peakCalendar = offPeakPowerData_->first;
201 Real offPeakHours = offPeakPowerData_->second;
202 Real total = 0;
203 for (const auto& kv : indices_) {
204 if (peakCalendar.isHoliday(kv.first))
205 total += weights_[kv.first] = 24;
206 else
207 total += weights_[kv.first] = offPeakHours;
208 }
209 for (auto& kv : weights_)
210 kv.second /= total;
211 }
212
213 // Must be called here after indices_ has been populated.
214 updateQuantity();
215
216}
217
218void CommodityIndexedAverageCashFlow::updateQuantity() {
219
220 using CQF = CommodityQuantityFrequency;
221 switch (quantityFrequency_)
222 {
223 case CQF::PerCalculationPeriod:
224 periodQuantity_ = quantity_;
225 if (unrealisedQuantity_) {
226 Date today = Settings::instance().evaluationDate();
227 if (startDate_ <= today && today < endDate_) {
228 // In both cases, unrealised equal to 0 should probably be an error but leave quantity unaltered.
229 if (offPeakPowerData_) {
230 QL_REQUIRE(!weights_.empty(), "Expected to have weights when we have off-peak power data.");
231 auto unrealised = accumulate(weights_.begin(), weights_.end(), 0.0,
232 [&today](Real sum, const pair<Date, Real>& p) {
233 return p.first > today ? sum + p.second : sum; });
234 if (unrealised > 0) {
235 periodQuantity_ /= unrealised;
236 }
237 } else {
238 auto unrealised = count_if(indices_.begin(), indices_.end(),
239 [&today](const pair<Date, ext::shared_ptr<CommodityIndex>>& p) { return p.first > today; });
240 if (unrealised > 0) {
241 periodQuantity_ = periodQuantity_ * indices_.size() / unrealised;
242 }
243 }
244 }
245 }
246 break;
247 case CQF::PerPricingDay:
248 periodQuantity_ = quantity_ * indices_.size();
249 break;
250 case CQF::PerHour:
251 if (offPeakPowerData_) {
252 Calendar peakCalendar = offPeakPowerData_->first;
253 Real offPeakHours = offPeakPowerData_->second;
254 periodQuantity_ = 0.0;
255 for (const auto& kv : indices_) {
256 if (peakCalendar.isHoliday(kv.first))
257 periodQuantity_ += 24.0 * quantity_;
258 else
259 periodQuantity_ += offPeakHours * quantity_;
260 }
261 } else {
262 QL_REQUIRE(hoursPerDay_ != Null<Natural>(), "If a commodity quantity frequency of PerHour is used, " <<
263 "a valid hoursPerDay value should be supplied.");
264 periodQuantity_ = quantity_ * indices_.size() * hoursPerDay_;
265 }
266 break;
267 case CQF::PerCalendarDay:
268 // Rarely used but kept because it has already been documented and released.
269 periodQuantity_ = quantity_ * ((endDate_ - startDate_ - 1.0) + (!excludeStartDate_ ? 1.0 : 0.0) +
270 (includeEndDate_ ? 1.0 : 0.0));
271 break;
272 default:
273 // Do nothing
274 break;
275 }
276
277}
278
279CommodityIndexedAverageLeg::CommodityIndexedAverageLeg(const Schedule& schedule,
280 const ext::shared_ptr<CommodityIndex>& index)
281 : schedule_(schedule), index_(index), paymentLag_(0), paymentCalendar_(NullCalendar()),
282 paymentConvention_(Unadjusted), pricingCalendar_(Calendar()),
283 paymentTiming_(CommodityIndexedAverageCashFlow::PaymentTiming::InArrears), useFuturePrice_(false),
284 deliveryDateRoll_(0), futureMonthOffset_(0), payAtMaturity_(false), includeEndDate_(true),
285 excludeStartDate_(true), useBusinessDays_(true),
286 quantityFrequency_(CommodityQuantityFrequency::PerCalculationPeriod), hoursPerDay_(Null<Natural>()),
287 dailyExpiryOffset_(Null<Natural>()), unrealisedQuantity_(false) {}
288
290 quantities_ = vector<Real>(1, quantity);
291 return *this;
292}
293
294CommodityIndexedAverageLeg& CommodityIndexedAverageLeg::withQuantities(const vector<Real>& quantities) {
295 quantities_ = quantities;
296 return *this;
297}
298
300 paymentLag_ = paymentLag;
301 return *this;
302}
303
305 paymentCalendar_ = paymentCalendar;
306 return *this;
307}
308
310 paymentConvention_ = paymentConvention;
311 return *this;
312}
313
315 pricingCalendar_ = pricingCalendar;
316 return *this;
317}
318
320 spreads_ = vector<Real>(1, spread);
321 return *this;
322}
323
324CommodityIndexedAverageLeg& CommodityIndexedAverageLeg::withSpreads(const vector<Real>& spreads) {
325 spreads_ = spreads;
326 return *this;
327}
328
329CommodityIndexedAverageLeg& CommodityIndexedAverageLeg::withGearings(Real gearing) {
330 gearings_ = vector<Real>(1, gearing);
331 return *this;
332}
333
334CommodityIndexedAverageLeg& CommodityIndexedAverageLeg::withGearings(const vector<Real>& gearings) {
335 gearings_ = gearings;
336 return *this;
337}
338
339CommodityIndexedAverageLeg&
342 return *this;
343}
344
346 useFuturePrice_ = flag;
347 return *this;
348}
349
351 deliveryDateRoll_ = deliveryDateRoll;
352 return *this;
353}
354
356 futureMonthOffset_ = futureMonthOffset;
357 return *this;
358}
359
361CommodityIndexedAverageLeg::withFutureExpiryCalculator(const ext::shared_ptr<FutureExpiryCalculator>& calc) {
362 calc_ = calc;
363 return *this;
364}
365
367 payAtMaturity_ = flag;
368 return *this;
369}
370
372 includeEndDate_ = flag;
373 return *this;
374}
375
377 excludeStartDate_ = flag;
378 return *this;
379}
380
382 paymentDates_ = paymentDates;
383 return *this;
384}
385
387 useBusinessDays_ = flag;
388 return *this;
389}
390
392 CommodityQuantityFrequency quantityFrequency) {
393 quantityFrequency_ = quantityFrequency;
394 return *this;
395}
396
398 hoursPerDay_ = hoursPerDay;
399 return *this;
400}
401
403 dailyExpiryOffset_ = dailyExpiryOffset;
404 return *this;
405}
406
408 unrealisedQuantity_ = flag;
409 return *this;
410}
411
413 fxIndex_ = fxIndex;
414 return *this;
415}
416
417CommodityIndexedAverageLeg& CommodityIndexedAverageLeg::withOffPeakPowerData(const boost::optional<pair<Calendar, Real> >& offPeakPowerData) {
418 offPeakPowerData_ = offPeakPowerData;
419 return *this;
420}
421
422CommodityIndexedAverageLeg::operator Leg() const {
423
424 // Number of commodity indexed average cashflows
425 Size numberCashflows = schedule_.size() - 1;
426
427 // Initial consistency checks
428 QL_REQUIRE(!quantities_.empty(), "No quantities given");
429 QL_REQUIRE(quantities_.size() <= numberCashflows,
430 "Too many quantities (" << quantities_.size() << "), only " << numberCashflows << " required");
431 if (useFuturePrice_) {
432 QL_REQUIRE(calc_, "CommodityIndexedCashFlow needs a valid future expiry calculator when using first future");
433 }
434
435 if (!paymentDates_.empty()) {
436 QL_REQUIRE(paymentDates_.size() == numberCashflows, "Expected the number of explicit payment dates ("
437 << paymentDates_.size()
438 << ") to equal the number of calculation periods ("
439 << numberCashflows << ")");
440 }
441
442 // If pay at maturity, populate payment date.
443 Date paymentDate;
444 if (payAtMaturity_) {
445 paymentDate = paymentCalendar_.advance(schedule_.dates().back(), paymentLag_ * Days, paymentConvention_);
446 }
447
448 // Leg to hold the result
449 // We always include the schedule start and schedule termination date in the averaging so the first and last
450 // coupon have special treatment here that overrides the includeEndDate_ and excludeStartDate_ flags
451 Leg leg;
452 leg.reserve(numberCashflows);
453 for (Size i = 0; i < numberCashflows; ++i) {
454
455 Date start = schedule_.date(i);
456 Date end = schedule_.date(i + 1);
457 bool excludeStart = i == 0 ? false : excludeStartDate_;
458 bool includeEnd = i == numberCashflows - 1 ? true : includeEndDate_;
459 Real quantity = detail::get(quantities_, i, 1.0);
460 Real spread = detail::get(spreads_, i, 0.0);
461 Real gearing = detail::get(gearings_, i, 1.0);
462
463 // If explicit payment dates provided, use them.
464 if (!paymentDates_.empty()) {
465 paymentDate = paymentDates_[i];
466 }
467
468 leg.push_back(ext::make_shared<CommodityIndexedAverageCashFlow>(
469 quantity, start, end, paymentLag_, paymentCalendar_, paymentConvention_, index_, pricingCalendar_, spread,
470 gearing, paymentTiming_, useFuturePrice_, deliveryDateRoll_, futureMonthOffset_, calc_, includeEnd,
471 excludeStart, paymentDate, useBusinessDays_, quantityFrequency_, hoursPerDay_, dailyExpiryOffset_,
472 unrealisedQuantity_, offPeakPowerData_, fxIndex_));
473 }
474
475 return leg;
476}
477
478} // namespace QuantExt
CommodityIndexedAverageCashFlow(QuantLib::Real quantity, const QuantLib::Date &startDate, const QuantLib::Date &endDate, const QuantLib::Date &paymentDate, const ext::shared_ptr< CommodityIndex > &index, const QuantLib::Calendar &pricingCalendar=QuantLib::Calendar(), QuantLib::Real spread=0.0, QuantLib::Real gearing=1.0, bool useFuturePrice=false, QuantLib::Natural deliveryDateRoll=0, QuantLib::Natural futureMonthOffset=0, const ext::shared_ptr< FutureExpiryCalculator > &calc=nullptr, bool includeEndDate=true, bool excludeStartDate=true, bool useBusinessDays=true, CommodityQuantityFrequency quantityFrequency=CommodityQuantityFrequency::PerCalculationPeriod, QuantLib::Natural hoursPerDay=QuantLib::Null< QuantLib::Natural >(), QuantLib::Natural dailyExpiryOffset=QuantLib::Null< QuantLib::Natural >(), bool unrealisedQuantity=false, const boost::optional< std::pair< QuantLib::Calendar, QuantLib::Real > > &offPeakPowerData=boost::none, const ext::shared_ptr< FxIndex > &fxIndex=nullptr)
Constructor taking an explicit paymentDate.
Helper class building a sequence of commodity indexed average cashflows.
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)
ext::shared_ptr< FutureExpiryCalculator > calc_
CommodityIndexedAverageLeg & withQuantityFrequency(CommodityQuantityFrequency quantityFrequency)
CommodityIndexedAverageLeg & withPaymentConvention(QuantLib::BusinessDayConvention paymentConvention)
CommodityIndexedAverageLeg & withPaymentDates(const std::vector< QuantLib::Date > &paymentDates)
boost::optional< std::pair< QuantLib::Calendar, QuantLib::Real > > offPeakPowerData_
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)
CommodityIndexedAverageCashFlow::PaymentTiming paymentTiming_
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)
Cash flow dependent on the average commodity spot price or future's settlement price over a period....
SimpleQuote & spread_
CommodityQuantityFrequency
Enumeration indicating the frequency associated with a commodity quantity.
set< Date > pricingDates(const Date &s, const Date &e, const Calendar &pricingCalendar, bool excludeStart, bool includeEnd, bool useBusinessDays)
Real sum(const Cash &c, const Cash &d)
Definition: bondbasket.cpp:107