35class OptionPaymentDateAdjuster {
37 virtual ~OptionPaymentDateAdjuster() =
default;
38 virtual void updatePaymentDate(
const QuantLib::Date& exiryDate, Date& paymentDate)
const {
44class OptionPaymentDataAdjuster :
public OptionPaymentDateAdjuster {
46 OptionPaymentDataAdjuster(
const OptionPaymentData& opd) : opd_(opd) {}
48 void updatePaymentDate(
const QuantLib::Date& expiryDate, Date& paymentDate)
const override {
49 if (opd_.rulesBased()) {
50 const Calendar& cal = opd_.calendar();
51 QL_REQUIRE(cal != Calendar(),
"Need a non-empty calendar for rules based payment date.");
52 paymentDate = cal.advance(expiryDate, opd_.lag(), Days, opd_.convention());
54 const vector<Date>& dates = opd_.dates();
55 QL_REQUIRE(dates.size() == 1,
"Need exactly one payment date for cash settled European option.");
56 paymentDate = dates[0];
61 OptionPaymentData opd_;
64class OptionStripPaymentDateAdjuster :
public OptionPaymentDateAdjuster {
66 OptionStripPaymentDateAdjuster(
const std::vector<Date>& expiryDates,
67 const CommoditySpreadOptionData::OptionStripData& stripData)
68 : calendar_(stripData.
calendar()), bdc_(stripData.bdc()), lag_(stripData.lag()) {
69 optionStripSchedule_ =
makeSchedule(stripData.schedule());
70 latestExpiryDateInStrip_.resize(optionStripSchedule_.size(), Date());
71 QL_REQUIRE(optionStripSchedule_.size() >= 2,
72 "Need at least a start and end date in the optionstripschedule. Please check the trade xml");
74 Date minExpiryDate = *std::min_element(expiryDates.begin(), expiryDates.end());
75 Date maxExpiryDate = *std::max_element(expiryDates.begin(), expiryDates.end());
76 Date minOptionStripDate =
77 *std::min_element(optionStripSchedule_.dates().begin(), optionStripSchedule_.dates().end());
78 Date maxOptionStripDate =
79 *std::max_element(optionStripSchedule_.dates().begin(), optionStripSchedule_.dates().end());
81 minOptionStripDate <= minExpiryDate && maxOptionStripDate > maxExpiryDate,
82 "optionStrips ending before latest expiry date, please check the optionstrip definition in the trade xml");
83 for (
const auto& e : expiryDates) {
84 auto it = std::upper_bound(optionStripSchedule_.begin(), optionStripSchedule_.end(), e);
85 if (it != optionStripSchedule_.end()) {
86 auto idx = std::distance(optionStripSchedule_.begin(), it);
87 if (e > latestExpiryDateInStrip_[idx])
88 latestExpiryDateInStrip_[idx] = e;
93 void updatePaymentDate(
const QuantLib::Date& expiryDate, Date& paymentDate)
const override {
95 std::upper_bound(optionStripSchedule_.dates().begin(), optionStripSchedule_.dates().end(), expiryDate);
96 if (upperBound != optionStripSchedule_.dates().end()) {
97 size_t idx = std::distance(optionStripSchedule_.dates().begin(), upperBound);
98 paymentDate = calendar_.advance(latestExpiryDateInStrip_[idx], lag_ * Days, bdc_);
105 vector<Date> latestExpiryDateInStrip_;
106 Schedule optionStripSchedule_;
108 BusinessDayConvention bdc_;
113 const std::vector<Date>& expiryDates) {
116 return QuantLib::ext::make_shared<OptionStripPaymentDateAdjuster>(expiryDates, optionData.
optionStrip().get());
118 return QuantLib::ext::make_shared<OptionPaymentDataAdjuster>(optionData.
optionData().
paymentData().get());
120 return QuantLib::ext::make_shared<OptionPaymentDateAdjuster>();
126 DLOG(
"CommoditySpreadOption::build() called for trade " <<
id());
129 additionalData_[
"isdaAssetClass"] = std::string(
"Commodity");
130 additionalData_[
"isdaBaseProduct"] = std::string(
"Other");
131 additionalData_[
"isdaSubProduct"] = std::string(
"");
133 additionalData_[
"isdaTransaction"] = std::string(
"");
136 auto legData_ = csoData_.legData();
137 auto optionData_ = csoData_.optionData();
138 auto strike_ = csoData_.strike();
140 QL_REQUIRE(legData_.size() == 2,
"Only two legs supported");
141 QL_REQUIRE(legData_[0].currency() == legData_[1].currency(),
"Both legs must have same currency");
142 QL_REQUIRE(legData_[0].isPayer() != legData_[1].isPayer(),
"Need one payer and one receiver leg");
144 if (!optionData_.style().empty()) {
146 QL_REQUIRE(exerciseType == QuantLib::Exercise::Type::European,
"Only European spread option supported");
150 npvCurrency_ = legData_[0].currency();
151 Size payerLegId = legData_[0].isPayer() ? 0 : 1;
154 std::vector<QuantLib::ext::shared_ptr<QuantExt::FxIndex>> fxIndexes(2,
nullptr);
159 QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder(tradeType_);
160 auto engineBuilder = QuantLib::ext::dynamic_pointer_cast<CommoditySpreadOptionEngineBuilder>(builder);
162 auto config = builder->configuration(MarketContext::pricing);
165 QL_REQUIRE(optionData_.exerciseDates().size() == 0,
166 "Only European spread option supported, expiry date is end_date of the period");
170 for (Size i = 0; i < legData_.size();
172 legPayers_.push_back(legData_[i].isPayer());
176 auto commLegData = (QuantLib::ext::dynamic_pointer_cast<CommodityFloatingLegData>(legData_[i].concreteLegData()));
177 QL_REQUIRE(commLegData,
"CommoditySpreadOption leg data should be of type CommodityFloating");
179 auto legBuilder = engineFactory->legBuilder(legData_[i].legType());
180 auto cflb = QuantLib::ext::dynamic_pointer_cast<CommodityFloatingLegBuilder>(legBuilder);
182 QL_REQUIRE(cflb,
"CommoditySpreadOption: Expected a CommodityFloatingLegBuilder for leg "
183 << i <<
" but got " << legData_[i].legType());
184 Leg leg = cflb->buildLeg(legData_[i], engineFactory, requiredFixings_, config);
187 QL_REQUIRE(!leg.empty(),
"CommoditySpreadOption: Leg " << i <<
" has no coupons");
188 auto index = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(leg.front())->index();
191 auto underlyingCcy = index->priceCurve()->currency();
192 auto tmpFx = commLegData->fxIndex();
194 QL_REQUIRE(underlyingCcy.code() == npvCurrency_,
195 "CommoditySpreadOption, inconsistent currencies: Settlement currency is "
196 << npvCurrency_ <<
", leg " << i + 1 <<
" currency " << legData_[i].currency()
197 <<
", underlying currency " << underlyingCcy <<
", no FxIndex provided");
199 QL_REQUIRE(underlyingCcy.code() != npvCurrency_,
200 "CommoditySpreadOption, inconsistent currencies: Settlement currency is "
201 << npvCurrency_ <<
", leg " << i + 1 <<
" currency " << legData_[i].currency()
202 <<
", underlying currency " << underlyingCcy <<
", FxIndex " << tmpFx <<
"provided");
203 auto domestic = npvCurrency_;
204 auto foreign = underlyingCcy.code();
205 fxIndexes[i] =
buildFxIndex(tmpFx, domestic, foreign, engineFactory->market(),
206 engineFactory->configuration(MarketContext::pricing));
208 if (commLegData->isAveraged()) {
209 for (
auto cf : leg) {
211 if (!fxIndexes[i]->fixingCalendar().isBusinessDay(
215 Date adjustedFixingDate = fxIndexes[i]->fixingCalendar().adjust(
fixingDate, Preceding);
216 requiredFixings_.addFixingDate(adjustedFixingDate, tmpFx);
218 requiredFixings_.addFixingDate(
fixingDate, tmpFx);
223 legs_.push_back(leg);
224 legCurrencies_.push_back(legData_[i].currency());
227 QL_REQUIRE(legs_[0].
size() == legs_[1].
size(),
228 "CommoditySpreadOption: the two legs must contain the same number of options.");
230 QL_REQUIRE(legs_[0].
size() > 0,
"CommoditySpreadOption: need at least one option, please check the trade xml");
233 Real bsInd = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);
235 QuantLib::ext::shared_ptr<QuantLib::Instrument> firstInstrument;
236 double firstMultiplier = 0.0;
237 vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
238 vector<Real> additionalMultipliers;
240 vector<Date> expiryDates;
241 for (
size_t i = 0; i < legs_[0].size(); ++i) {
242 auto longFlow = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(legs_[1 - payerLegId][i]);
243 auto shortFlow = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(legs_[payerLegId][i]);
244 expiryDates.push_back(std::max(longFlow->lastPricingDate(), shortFlow->lastPricingDate()));
249 for (
size_t i = 0; i < legs_[0].size(); ++i) {
250 auto longFlow = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(legs_[1 - payerLegId][i]);
251 auto shortFlow = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityCashFlow>(legs_[payerLegId][i]);
253 QuantLib::Real quantity = longFlow->periodQuantity();
255 QL_REQUIRE(quantity == shortFlow->periodQuantity(),
"all cashflows must refer to the same quantity");
257 QuantLib::Date expiryDate = expiryDates[i];
259 QuantLib::Date paymentDate = longFlow->date();
261 QL_REQUIRE(paymentDate == shortFlow->date(),
262 "all cashflows must refer to the same paymentDate, its used as the settlementDate of the option");
264 paymentDateAdjuster->updatePaymentDate(expiryDate, paymentDate);
266 QL_REQUIRE(paymentDate >= expiryDate,
"Payment date must be greater than or equal to expiry date.");
268 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiryDate);
272 maturity_ = maturity_ == Date() ? paymentDate : std::max(maturity_, paymentDate);
275 QuantLib::ext::shared_ptr<QuantExt::CommoditySpreadOption> spreadOption =
276 QuantLib::ext::make_shared<QuantExt::CommoditySpreadOption>(longFlow, shortFlow, exercise, quantity, strike_,
277 optionType, paymentDate, fxIndexes[1 - payerLegId],
278 fxIndexes[1 - payerLegId]);
281 QuantLib::ext::shared_ptr<PricingEngine> commoditySpreadOptionEngine =
282 engineBuilder->engine(ccy, longFlow->index(), shortFlow->index(),
id());
283 spreadOption->setPricingEngine(commoditySpreadOptionEngine);
284 setSensitivityTemplate(*engineBuilder);
286 additionalInstruments.push_back(spreadOption);
287 additionalMultipliers.push_back(bsInd);
289 firstInstrument = spreadOption;
290 firstMultiplier = bsInd;
295 auto configuration = engineBuilder->configuration(MarketContext::pricing);
296 maturity_ = std::max(maturity_, addPremiums(additionalInstruments, additionalMultipliers, firstMultiplier,
297 optionData_.premiumData(), -bsInd, ccy, engineFactory, configuration));
299 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(firstInstrument, firstMultiplier, additionalInstruments,
300 additionalMultipliers);
301 if (!optionData_.premiumData().premiumData().empty()) {
302 auto premium = optionData_.premiumData().premiumData().front();
303 additionalData_[
"premiumAmount"] = -bsInd * premium.amount;
304 additionalData_[
"premiumPaymentDate"] = premium.payDate;
305 additionalData_[
"premiumCurrency"] = premium.ccy;
309std::map<ore::data::AssetClass, std::set<std::string>>
310CommoditySpreadOption::underlyingIndices(
const QuantLib::ext::shared_ptr<ReferenceDataManager>& referenceDataManager)
const {
311 std::map<ore::data::AssetClass, std::set<std::string>> result;
312 auto legData = csoData_.legData();
313 for (
const auto& leg : legData) {
314 set<string> indices = leg.indices();
315 for (
auto ind : indices) {
316 QuantLib::ext::shared_ptr<Index> index =
parseIndex(ind);
318 if (
auto ci = QuantLib::ext::dynamic_pointer_cast<QuantExt::CommodityIndex>(index)) {
327void CommoditySpreadOption::fromXML(
XMLNode* node) {
328 Trade::fromXML(node);
329 XMLNode* csoNode = XMLUtils::getChildNode(node,
"CommoditySpreadOptionData");
330 csoData_.fromXML(csoNode);
333 XMLNode* node = Trade::toXML(doc);
334 auto csoNode = csoData_.toXML(doc);
335 XMLUtils::appendNode(node, csoNode);
339void CommoditySpreadOptionData::fromXML(
XMLNode* csoNode) {
340 XMLUtils::checkNode(csoNode,
"CommoditySpreadOptionData");
342 XMLNode* optionDataNode = XMLUtils::getChildNode(csoNode,
"OptionData");
343 QL_REQUIRE(optionDataNode,
"Invalid CommmoditySpreadOption trade xml: found no OptionData Node");
345 optionData_.fromXML(optionDataNode);
346 strike_ = XMLUtils::getChildValueAsDouble(csoNode,
"SpreadStrike",
true);
348 vector<XMLNode*> nodes = XMLUtils::getChildrenNodes(csoNode,
"LegData");
349 QL_REQUIRE(nodes.size() == 2,
"CommoditySpreadOption: Exactly two LegData nodes expected");
350 for (
auto& node : nodes) {
353 legData_.push_back(*ld);
356 XMLNode* optionStripNode = XMLUtils::getChildNode(csoNode,
"OptionStripPaymentDates");
357 if (optionStripNode) {
359 optionStrip_->fromXML(optionStripNode);
362 QL_REQUIRE(legData_[0].isPayer() != legData_[1].isPayer(),
363 "CommoditySpreadOption: both a long and a short Assets are required.");
368 for (
size_t i = 0; i < legData_.size(); ++i) {
369 XMLUtils::appendNode(csoNode, legData_[i].toXML(doc));
371 XMLUtils::appendNode(csoNode, optionData_.toXML(doc));
372 XMLUtils::addChild(doc, csoNode,
"SpreadStrike", strike_);
373 if (optionStrip_.has_value()) {
374 XMLUtils::appendNode(csoNode, optionStrip_->toXML(doc));
380void CommoditySpreadOptionData::OptionStripData::fromXML(
XMLNode* node) {
381 XMLUtils::checkNode(node,
"OptionStripPaymentDates");
382 XMLNode* optionStripScheduleNode = XMLUtils::getChildNode(node,
"OptionStripDefinition");
383 QL_REQUIRE(optionStripScheduleNode,
"Schedule required to define the option strips");
384 schedule_.fromXML(optionStripScheduleNode);
385 calendar_ =
parseCalendar(XMLUtils::getChildValue(node,
"PaymentCalendar",
false,
"NullCalendar"));
386 lag_ =
parseInteger(XMLUtils::getChildValue(node,
"PaymentLag",
false,
"0"));
392 auto tmp = schedule_.toXML(doc);
393 XMLUtils::setNodeName(doc, tmp,
"OptionStripDefinition");
394 XMLUtils::appendNode(node, tmp);
395 XMLUtils::addChild(doc, node,
"PaymentCalendar",
to_string(calendar_));
396 XMLUtils::addChild(doc, node,
"PaymentLag",
to_string(lag_));
397 XMLUtils::addChild(doc, node,
"PaymentConvention",
to_string(bdc_));
const ore::data::OptionData & optionData() const
boost::optional< OptionStripData > optionStrip()
void build(const QuantLib::ext::shared_ptr< ore::data::EngineFactory > &engineFactory) override
Implement the build method.
const boost::optional< OptionPaymentData > & paymentData() const
Small XML Document wrapper class.
XMLNode * allocNode(const string &nodeName)
util functions that wrap rapidxml
Commodity fixed and floating leg builders.
QuantLib::ext::shared_ptr< LegAdditionalData > createLegData()
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Exercise::Type parseExerciseType(const std::string &s)
Convert text to QuantLib::Exercise::Type.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Position::Type parsePositionType(const std::string &s)
Convert text to QuantLib::Position::Type.
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
QuantLib::ext::shared_ptr< Index > parseIndex(const string &s)
Convert std::string to QuantLib::Index.
Integer parseInteger(const string &s)
Convert text to QuantLib::Integer.
Option::Type parseOptionType(const std::string &s)
Convert text to QuantLib::Option::Type.
#define DLOG(text)
Logging Macro (Level = Debug)
market data related utilties
QuantLib::Date fixingDate(const QuantLib::Date &d, const QuantLib::Period obsLag, const QuantLib::Frequency freq, bool interpolated)
Size size(const ValueType &v)
void reset(const ASTNodePtr root)
std::string to_string(const LocationInfo &l)
QuantLib::ext::shared_ptr< OptionPaymentDateAdjuster > makeOptionPaymentDateAdjuster(CommoditySpreadOptionData &optionData, const std::vector< Date > &expiryDates)
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)
Schedule makeSchedule(const ScheduleDates &data)
string conversion utilities