1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008 J. Erik Radmall
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include <ql/experimental/commodities/commoditysettings.hpp>
21#include <ql/experimental/commodities/energybasisswap.hpp>
22#include <utility>
23
24namespace QuantLib {
25
26 EnergyBasisSwap::EnergyBasisSwap(const Calendar& calendar,
27 ext::shared_ptr<CommodityIndex> spreadIndex,
28 ext::shared_ptr<CommodityIndex> payIndex,
29 ext::shared_ptr<CommodityIndex> receiveIndex,
30 bool spreadToPayLeg,
31 const Currency& payCurrency,
32 const Currency& receiveCurrency,
33 const PricingPeriods& pricingPeriods,
34 CommodityUnitCost basis,
35 const CommodityType& commodityType,
36 const ext::shared_ptr<SecondaryCosts>& secondaryCosts,
37 Handle<YieldTermStructure> payLegTermStructure,
38 Handle<YieldTermStructure> receiveLegTermStructure,
39 Handle<YieldTermStructure> discountTermStructure)
40 : EnergySwap(
41 calendar, payCurrency, receiveCurrency, pricingPeriods, commodityType, secondaryCosts),
42 spreadIndex_(std::move(spreadIndex)), payIndex_(std::move(payIndex)),
43 receiveIndex_(std::move(receiveIndex)), spreadToPayLeg_(spreadToPayLeg),
44 basis_(std::move(basis)), payLegTermStructure_(std::move(payLegTermStructure)),
45 receiveLegTermStructure_(std::move(receiveLegTermStructure)),
46 discountTermStructure_(std::move(discountTermStructure)) {
47 QL_REQUIRE(!pricingPeriods_.empty(), "no payment dates");
48 registerWith(h: spreadIndex_);
49 registerWith(h: payIndex_);
50 registerWith(h: receiveIndex_);
51 }
52
53 void EnergyBasisSwap::performCalculations() const {
54
55 try {
56
57 if (payIndex_->empty()) {
58 if (payIndex_->forwardCurveEmpty()) {
59 QL_FAIL("index [" + payIndex_->name() +
60 "] does not have any quotes or forward prices");
61 } else {
62 addPricingError(errorLevel: PricingError::Warning,
63 error: "index [" + payIndex_->name() +
64 "] does not have any quotes; "
65 "using forward prices from [" +
66 payIndex_->forwardCurve()->name() + "]");
67 }
68 }
69 if (receiveIndex_->empty()) {
70 if (receiveIndex_->forwardCurveEmpty()) {
71 QL_FAIL("index [" + receiveIndex_->name() +
72 "] does not have any quotes or forward prices");
73 } else {
74 addPricingError(errorLevel: PricingError::Warning,
75 error: "index [" + receiveIndex_->name() +
76 "] does not have any quotes; "
77 "using forward prices from [" +
78 receiveIndex_->forwardCurve()->name() +
79 "]");
80 }
81 }
82
83 NPV_ = 0.0;
84 additionalResults_.clear();
85 dailyPositions_.clear();
86 paymentCashFlows_.clear();
87
88 Date evaluationDate = Settings::instance().evaluationDate();
89
90 const Currency& baseCurrency =
91 CommoditySettings::instance().currency();
92 const UnitOfMeasure baseUnitOfMeasure =
93 CommoditySettings::instance().unitOfMeasure();
94
95 Real quantityUomConversionFactor =
96 calculateUomConversionFactor(
97 commodityType: pricingPeriods_[0]->quantity().commodityType(),
98 fromUnitOfMeasure: baseUnitOfMeasure,
99 toUnitOfMeasure: pricingPeriods_[0]->quantity().unitOfMeasure());
100 Real payIndexUomConversionFactor =
101 calculateUomConversionFactor(commodityType: payIndex_->commodityType(),
102 fromUnitOfMeasure: payIndex_->unitOfMeasure(),
103 toUnitOfMeasure: baseUnitOfMeasure);
104 Real receiveIndexUomConversionFactor =
105 calculateUomConversionFactor(commodityType: receiveIndex_->commodityType(),
106 fromUnitOfMeasure: receiveIndex_->unitOfMeasure(),
107 toUnitOfMeasure: baseUnitOfMeasure);
108
109 Real payIndexFxConversionFactor =
110 calculateFxConversionFactor(fromCurrency: payIndex_->currency(),
111 toCurrency: baseCurrency, evaluationDate);
112 Real receiveIndexFxConversionFactor =
113 calculateFxConversionFactor(fromCurrency: receiveIndex_->currency(),
114 toCurrency: baseCurrency, evaluationDate);
115 Real payLegFxConversionFactor =
116 calculateFxConversionFactor(fromCurrency: baseCurrency, toCurrency: payCurrency_,
117 evaluationDate);
118 Real receiveLegFxConversionFactor =
119 calculateFxConversionFactor(fromCurrency: baseCurrency, toCurrency: receiveCurrency_,
120 evaluationDate);
121
122 Real basisUomConversionFactor =
123 calculateUomConversionFactor(
124 commodityType: pricingPeriods_[0]->quantity().commodityType(),
125 fromUnitOfMeasure: basis_.unitOfMeasure(), toUnitOfMeasure: baseUnitOfMeasure);
126 Real basisFxConversionFactor =
127 calculateFxConversionFactor(fromCurrency: baseCurrency,
128 toCurrency: basis_.amount().currency(),
129 evaluationDate);
130
131 Real basisValue = basis_.amount().value() *
132 basisUomConversionFactor * basisFxConversionFactor;
133
134 Date lastPayIndexQuoteDate = payIndex_->lastQuoteDate();
135 Date lastReceiveIndexQuoteDate = receiveIndex_->lastQuoteDate();
136
137 if (lastPayIndexQuoteDate < evaluationDate - 1) {
138 std::ostringstream message;
139 message << "index [" << payIndex_->name()
140 << "] has last quote date of "
141 << io::iso_date(lastPayIndexQuoteDate);
142 addPricingError(errorLevel: PricingError::Warning, error: message.str());
143 }
144 if (lastReceiveIndexQuoteDate < evaluationDate - 1) {
145 std::ostringstream message;
146 message << "index [" << receiveIndex_->name()
147 << "] has last quote date of "
148 << io::iso_date(lastReceiveIndexQuoteDate);
149 addPricingError(errorLevel: PricingError::Warning, error: message.str());
150 }
151
152 Date lastQuoteDate = std::min(a: lastPayIndexQuoteDate,
153 b: lastReceiveIndexQuoteDate);
154
155 Real totalQuantityAmount = 0;
156
157 // price each period
158 for (const auto& pricingPeriod : pricingPeriods_) {
159 Integer periodDayCount = 0;
160
161 // get the index quotes
162 Date periodStartDate =
163 calendar_.adjust(pricingPeriod->startDate());
164 for (Date stepDate = periodStartDate;
165 stepDate <= pricingPeriod->endDate();
166 stepDate = calendar_.advance(date: stepDate, period: 1*Days)) {
167
168 bool unrealized = stepDate > evaluationDate;
169 Real payQuoteValue = 0;
170 Real receiveQuoteValue = 0;
171
172 if (stepDate <= lastQuoteDate) {
173 payQuoteValue = payIndex_->price(date: stepDate);
174 receiveQuoteValue = receiveIndex_->price(date: stepDate);
175 } else {
176 payQuoteValue = payIndex_->forwardPrice(date: stepDate);
177 receiveQuoteValue =
178 receiveIndex_->forwardPrice(date: stepDate);
179 }
180
181 if (payQuoteValue == 0) {
182 std::ostringstream message;
183 message << "pay quote value for curve ["
184 << payIndex_->name() << "] is 0 for date "
185 << io::iso_date(stepDate);
186 addPricingError(errorLevel: PricingError::Warning, error: message.str());
187 }
188 if (receiveQuoteValue == 0) {
189 std::ostringstream message;
190 message << "receive quote value for curve ["
191 << receiveIndex_->name() << "] is 0 for date "
192 << io::iso_date(stepDate);
193 addPricingError(errorLevel: PricingError::Warning, error: message.str());
194 }
195
196 QL_REQUIRE(payQuoteValue != Null<Real>(),
197 "curve [" << payIndex_->name() <<
198 "] missing value for pricing date: "
199 << stepDate);
200 QL_REQUIRE(receiveQuoteValue != Null<Real>(),
201 "curve [" << receiveIndex_->name() <<
202 "] missing value for pricing date: "
203 << stepDate);
204
205 Real payLegPriceValue =
206 payQuoteValue * payIndexUomConversionFactor *
207 payIndexFxConversionFactor;
208 Real receiveLegPriceValue =
209 receiveQuoteValue * receiveIndexUomConversionFactor *
210 receiveIndexFxConversionFactor;
211
212 if (spreadToPayLeg_)
213 payLegPriceValue += basisValue;
214 else
215 receiveLegPriceValue += basisValue;
216
217 dailyPositions_[stepDate] =
218 EnergyDailyPosition(stepDate, payLegPriceValue,
219 receiveLegPriceValue, unrealized);
220 periodDayCount++;
221 }
222
223 Real periodQuantityAmount =
224 pricingPeriod->quantity().amount() *
225 quantityUomConversionFactor;
226 totalQuantityAmount += periodQuantityAmount;
227
228 Real avgDailyQuantityAmount =
229 periodDayCount == 0 ? Real(0) :
230 periodQuantityAmount / periodDayCount;
231
232 Real payLegValue = 0;
233 Real receiveLegValue = 0;
234 for (auto dpi = dailyPositions_.find(x: periodStartDate);
235 dpi != dailyPositions_.end() && dpi->first <= pricingPeriod->endDate();
236 ++dpi) {
237 EnergyDailyPosition& dailyPosition = dpi->second;
238 dailyPosition.quantityAmount = avgDailyQuantityAmount;
239 dailyPosition.riskDelta =
240 (-dailyPosition.payLegPrice + dailyPosition.receiveLegPrice) * avgDailyQuantityAmount;
241 payLegValue += -dailyPosition.payLegPrice * avgDailyQuantityAmount;
242 receiveLegValue += dailyPosition.receiveLegPrice * avgDailyQuantityAmount;
243 }
244
245 Real discountFactor = 1;
246 Real payLegDiscountFactor = 1;
247 Real receiveLegDiscountFactor = 1;
248 if (pricingPeriod->paymentDate() >= evaluationDate + 2 /* settlement days*/) {
249 discountFactor =
250 discountTermStructure_->discount(
251 d: pricingPeriod->paymentDate());
252 payLegDiscountFactor =
253 payLegTermStructure_->discount(
254 d: pricingPeriod->paymentDate());
255 receiveLegDiscountFactor =
256 receiveLegTermStructure_->discount(
257 d: pricingPeriod->paymentDate());
258 }
259
260 Real uDelta = receiveLegValue + payLegValue;
261 Real dDelta = (receiveLegValue * receiveLegDiscountFactor) +
262 (payLegValue * payLegDiscountFactor);
263 Real pmtFxConversionFactor =
264 (dDelta > 0) ? payLegFxConversionFactor : receiveLegFxConversionFactor;
265 Currency pmtCurrency =
266 (dDelta > 0) ? receiveCurrency_ : payCurrency_;
267 Real pmtDiscountFactor =
268 (dDelta > 0) ? receiveLegDiscountFactor : payLegDiscountFactor;
269
270 paymentCashFlows_[pricingPeriod->paymentDate()] =
271 ext::make_shared<CommodityCashFlow> (
272 args: pricingPeriod->paymentDate(),
273 args: Money(baseCurrency,
274 uDelta * discountFactor),
275 args: Money(baseCurrency, uDelta),
276 args: Money(pmtCurrency,
277 dDelta * pmtFxConversionFactor),
278 args: Money(pmtCurrency,
279 uDelta * pmtFxConversionFactor),
280 args&: discountFactor,
281 args&: pmtDiscountFactor,
282 args: pricingPeriod->paymentDate() <= evaluationDate);
283
284 calculateSecondaryCostAmounts(
285 commodityType: pricingPeriods_[0]->quantity().commodityType(),
286 totalQuantityValue: totalQuantityAmount, evaluationDate);
287
288 NPV_ += dDelta;
289 }
290
291 QL_REQUIRE(!paymentCashFlows_.empty(), "no cashflows");
292
293 for (SecondaryCostAmounts::const_iterator i =
294 secondaryCostAmounts_.begin();
295 i != secondaryCostAmounts_.end(); ++i) {
296 Real amount = i->second.value();
297 NPV_ -= amount;
298 }
299
300 additionalResults_["dailyPositions"] = dailyPositions_;
301
302 } catch (const QuantLib::Error& e) {
303 addPricingError(errorLevel: PricingError::Error, error: e.what());
304 throw;
305 } catch (const std::exception& e) {
306 addPricingError(errorLevel: PricingError::Error, error: e.what());
307 throw;
308 }
309 }
310
311}
312
313

source code of quantlib/ql/experimental/commodities/energybasisswap.cpp