Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
flexiswap.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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
21#include <qle/instruments/flexiswap.hpp>
22
27
28#include <ql/cashflows/fixedratecoupon.hpp>
29#include <ql/cashflows/floatingratecoupon.hpp>
30
31using namespace QuantLib;
32
33namespace ore {
34namespace data {
35
36void FlexiSwap::build(const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory) {
37
38 LOG("FlexiSwap::build() for id \"" << id() << "\" called.");
39
40 // ISDA taxonomy
41 additionalData_["isdaAssetClass"] = string("Interest Rate");
42 additionalData_["isdaBaseProduct"] = string("Exotic");
43 additionalData_["isdaSubProduct"] = string("");
44 additionalData_["isdaTransaction"] = string("");
45
46 QL_REQUIRE(swap_.size() == 2, "swap must have 2 legs");
47 QL_REQUIRE(swap_[0].currency() == swap_[1].currency(), "swap must be single currency");
48
49 string ccy_str = swap_[0].currency();
50 Currency currency = parseCurrency(ccy_str);
51
52 Size fixedLegIndex, floatingLegIndex;
53 if (swap_[0].legType() == "Floating" && swap_[1].legType() == "Fixed") {
54 floatingLegIndex = 0;
55 fixedLegIndex = 1;
56 } else if (swap_[1].legType() == "Floating" && swap_[0].legType() == "Fixed") {
57 floatingLegIndex = 1;
58 fixedLegIndex = 0;
59 } else {
60 QL_FAIL("Invalid leg types " << swap_[0].legType() << " + " << swap_[1].legType());
61 }
62
63 QuantLib::ext::shared_ptr<FixedLegData> fixedLegData =
64 QuantLib::ext::dynamic_pointer_cast<FixedLegData>(swap_[fixedLegIndex].concreteLegData());
65 QuantLib::ext::shared_ptr<FloatingLegData> floatingLegData =
66 QuantLib::ext::dynamic_pointer_cast<FloatingLegData>(swap_[floatingLegIndex].concreteLegData());
67
68 QL_REQUIRE(fixedLegData != nullptr, "expected fixed leg data");
69 QL_REQUIRE(floatingLegData != nullptr, "expected floating leg data");
70
71 QuantLib::ext::shared_ptr<EngineBuilder> tmp = engineFactory->builder("FlexiSwap");
72 auto builder = QuantLib::ext::dynamic_pointer_cast<FlexiSwapBGSEngineBuilderBase>(tmp);
73 QL_REQUIRE(builder, "No Flexi-Swap Builder found for \"" << id() << "\"");
74
75 Schedule fixedSchedule = makeSchedule(swap_[fixedLegIndex].schedule());
76 Schedule floatingSchedule = makeSchedule(swap_[floatingLegIndex].schedule());
77 vector<Real> fixedNominal = buildScheduledVectorNormalised(
78 swap_[fixedLegIndex].notionals(), swap_[fixedLegIndex].notionalDates(), fixedSchedule, 0.0);
79 vector<Real> floatNominal = buildScheduledVectorNormalised(
80 swap_[floatingLegIndex].notionals(), swap_[floatingLegIndex].notionalDates(), floatingSchedule, 0.0);
81 vector<Real> fixedRate =
82 buildScheduledVectorNormalised(fixedLegData->rates(), fixedLegData->rateDates(), fixedSchedule, 0.0);
83 vector<Real> spreads = buildScheduledVectorNormalised(floatingLegData->spreads(), floatingLegData->spreadDates(),
84 floatingSchedule, 0.0);
85 vector<Real> gearings = buildScheduledVectorNormalised(floatingLegData->gearings(), floatingLegData->gearingDates(),
86 floatingSchedule, 1.0);
87 vector<Real> caps = buildScheduledVectorNormalised(floatingLegData->caps(), floatingLegData->capDates(),
88 floatingSchedule, (Real)Null<Real>());
89 vector<Real> floors = buildScheduledVectorNormalised(floatingLegData->floors(), floatingLegData->floorDates(),
90 floatingSchedule, (Real)Null<Real>());
91 floatingIndex_ = floatingLegData->index();
92 DayCounter fixedDayCounter = parseDayCounter(swap_[fixedLegIndex].dayCounter());
93 Handle<IborIndex> index =
94 engineFactory->market()->iborIndex(floatingIndex_, builder->configuration(MarketContext::pricing));
95 DayCounter floatingDayCounter = parseDayCounter(swap_[floatingLegIndex].dayCounter());
96 BusinessDayConvention paymentConvention = parseBusinessDayConvention(swap_[floatingLegIndex].paymentConvention());
97 VanillaSwap::Type type = swap_[fixedLegIndex].isPayer() ? VanillaSwap::Payer : VanillaSwap::Receiver;
98
99 vector<Real> lowerNotionalBounds = fixedNominal; // default, no optionality
100 std::vector<bool> notionalCanBeDecreased(fixedNominal.size(), true);
101
102 // check we have at most one optionality description
103
104 QL_REQUIRE(lowerNotionalBounds_.empty() || exerciseDates_.empty(),
105 "can not have lower notional bounds and exercise dates / types / values specified at the same time");
106
107 // optionality is given by lower notional bounds
108
109 if (!lowerNotionalBounds_.empty()) {
112 DLOG("optionality is given by lower notional bounds");
113 }
114
115 // optionality is given by exercise dates, types, values
116
117 // FIXME this is an approximation, we build an approximate instrument here using the global lower
118 // notional bounds; for a correct representation we would need local bounds that depend on the
119 // current notional of the swap; see below where the approximation occurs specifically
120
121 if (!exerciseDates_.empty()) {
122 DLOG("optionality is given by exercise dates, types, values");
123
124 // FIXME: we also ignore the notice period at this stage of the implementation, the notice day
125 // is always assumed to lie on the fixing date of the corresponding float period of the swap
126
127 // start with no optionality
128 notionalCanBeDecreased = std::vector<bool>(fixedNominal.size(), false);
129
130 // loop over exercise dates and update lower notional bounds belonging to that exercise
131 Date previousExerciseDate = Null<Date>();
132 for (Size i = 0; i < exerciseDates_.size(); ++i) {
133 Date d = parseDate(exerciseDates_[i]);
134 QL_REQUIRE(exerciseValues_[i] > 0.0 || close_enough(exerciseValues_[i], 0.0),
135 "exercise value #" << i << " (" << exerciseValues_[i] << ") must be non-negative");
136 QL_REQUIRE(i == 0 || previousExerciseDate < d, "exercise dates must be strictly increasing, got "
137 << QuantLib::io::iso_date(previousExerciseDate)
138 << " and " << QuantLib::io::iso_date(d) << " as #" << i
139 << " and #" << i + 1);
140 previousExerciseDate = d;
141 // determine the fixed period that follows the exercise date
142 Size exerciseIdx = std::lower_bound(fixedSchedule.dates().begin(), fixedSchedule.dates().end(), d) -
143 fixedSchedule.dates().begin();
144 if (exerciseIdx >= fixedSchedule.dates().size() - 1) {
145 DLOG("exercise date "
146 << QuantLib::io::iso_date(d)
147 << " ignored since there is no whole fixed leg period with accrual start >= exercise date");
148 continue;
149 }
150 notionalCanBeDecreased[exerciseIdx] = true;
151 if (exerciseTypes_[i] == "ReductionUpToLowerBound") {
152 for (Size j = exerciseIdx; j < lowerNotionalBounds.size(); ++j) {
154 }
155 } else if (exerciseTypes_[i] == "ReductionByAbsoluteAmount" ||
156 exerciseTypes_[i] == "ReductionUpToAbsoluteAmount") {
157 // FIXME we just assume that all prepayment option before this one here were exercised
158 // and reduce the lower notional bounds by the current exercise amount; we also treat
159 // "by" the same as "up to"
160 for (Size j = exerciseIdx; j < lowerNotionalBounds.size(); ++j) {
161 lowerNotionalBounds[j] = std::max(lowerNotionalBounds[j] - exerciseValues_[i], 0.0);
162 }
163 } else {
164 QL_FAIL("exercise type '" << exerciseTypes_[i]
165 << "' unknown, expected ReductionUpToLowerBound, ReductionByAbsoluteAmount, "
166 "ReductionUpToAbsoluteAmount");
167 }
168 }
169 }
170
171 DLOG("fixedPeriod#,notional,lowerNotionalBound,canBeReduced");
172 for (Size i = 0; i < lowerNotionalBounds.size(); ++i) {
173 DLOG(i << "," << fixedNominal.at(i) << "," << lowerNotionalBounds[i] << "," << std::boolalpha
174 << notionalCanBeDecreased[i]);
175 }
176
177 // set up ql instrument
178
180
181 auto flexiSwap = QuantLib::ext::make_shared<QuantExt::FlexiSwap>(
182 type, fixedNominal, floatNominal, fixedSchedule, fixedRate, fixedDayCounter, floatingSchedule, *index, gearings,
183 spreads, caps, floors, floatingDayCounter, lowerNotionalBounds, optionLongShort, notionalCanBeDecreased,
184 paymentConvention);
185
186 auto fixLeg = flexiSwap->leg(0);
187 auto fltLeg = flexiSwap->leg(1);
188
189 // set coupon pricers if needed (for flow report, discounting swap engine, not used in LGM engine)
190
191 bool hasCapsFloors = false;
192 for (auto const& k : caps) {
193 if (k != Null<Real>())
194 hasCapsFloors = true;
195 }
196 for (auto const& k : floors) {
197 if (k != Null<Real>())
198 hasCapsFloors = true;
199 }
200 if (hasCapsFloors) {
201 QuantLib::ext::shared_ptr<EngineBuilder> cfBuilder = engineFactory->builder("CapFlooredIborLeg");
202 QL_REQUIRE(cfBuilder, "No builder found for CapFlooredIborLeg");
203 QuantLib::ext::shared_ptr<CapFlooredIborLegEngineBuilder> cappedFlooredIborBuilder =
204 QuantLib::ext::dynamic_pointer_cast<CapFlooredIborLegEngineBuilder>(cfBuilder);
205 QL_REQUIRE(cappedFlooredIborBuilder != nullptr, "expected CapFlooredIborLegEngineBuilder");
206 QuantLib::ext::shared_ptr<FloatingRateCouponPricer> couponPricer =
207 cappedFlooredIborBuilder->engine(IndexNameTranslator::instance().oreName(index->name()));
208 QuantLib::setCouponPricer(fltLeg, couponPricer);
209 }
210
211 // determine expiries and strikes for calibration basket (simple approach, a la summit)
212 std::vector<Date> expiryDates;
213 std::vector<Real> strikes;
214 Date today = Settings::instance().evaluationDate();
215 Size legRatio = fltLeg.size() / fixLeg.size(); // no remainder by construction of a flexi swap
216 for (Size i = 0; i < fltLeg.size(); ++i) {
217 auto fltcpn = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(fltLeg[i]);
218 if (fltcpn != nullptr && fltcpn->fixingDate() > today && i % legRatio == 0) {
219 expiryDates.push_back(fltcpn->fixingDate());
220 auto fixcpn = QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(fixLeg[i / legRatio]);
221 QL_REQUIRE(fixcpn != nullptr, "FlexiSwap Builder: expected fixed rate coupon");
222 strikes.push_back(fixcpn->rate() - fltcpn->spread());
223 }
224 }
225
226 // set pricing engine, init instrument and other trade members
227
228 flexiSwap->setPricingEngine(
229 builder->engine(id(), "", index.empty() ? ccy_str : IndexNameTranslator::instance().oreName(index->name()),
230 expiryDates, flexiSwap->maturityDate(), strikes));
231 setSensitivityTemplate(*builder);
232
233 // FIXME this won't work for exposure, currently not supported
234 instrument_ = QuantLib::ext::make_shared<VanillaInstrument>(flexiSwap);
235
236 npvCurrency_ = ccy_str;
237 notional_ = std::max(currentNotional(fixLeg), currentNotional(fltLeg));
238 notionalCurrency_ = ccy_str;
239 legCurrencies_ = vector<string>(2, ccy_str);
240 legs_ = {fixLeg, fltLeg};
241 legPayers_ = {swap_[fixedLegIndex].isPayer(), swap_[floatingLegIndex].isPayer()};
242 maturity_ = flexiSwap->maturityDate();
243 addToRequiredFixings(fltLeg, QuantLib::ext::make_shared<FixingDateGetter>(requiredFixings_));
244}
245
247 Trade::fromXML(node);
248 XMLNode* swapNode = XMLUtils::getChildNode(node, "FlexiSwapData");
249 QL_REQUIRE(swapNode, "FlexiSwap::fromXML(): FlexiSwapData not found");
250 // optionality given by lower notional bounds
251 lowerNotionalBounds_ = XMLUtils::getChildrenValuesWithAttributes<Real>(
252 swapNode, "LowerNotionalBounds", "Notional", "startDate", lowerNotionalBoundsDates_, &parseReal);
253 // optionality given by exercise dates, types and values
255 exerciseDates_.clear();
256 exerciseTypes_.clear();
257 exerciseValues_.clear();
258 XMLNode* prepayNode = XMLUtils::getChildNode(swapNode, "Prepayment");
259 if (prepayNode) {
260 noticePeriod_ = XMLUtils::getChildValue(prepayNode, "NoticePeriod", false);
261 noticeCalendar_ = XMLUtils::getChildValue(prepayNode, "NoticeCalendar", false);
262 noticeConvention_ = XMLUtils::getChildValue(prepayNode, "NoticeConvention", false);
263 XMLNode* optionsNode = XMLUtils::getChildNode(prepayNode, "PrepaymentOptions");
264 if (optionsNode) {
265 auto prepayOptionNodes = XMLUtils::getChildrenNodes(optionsNode, "PrepaymentOption");
266 for (auto const n : prepayOptionNodes) {
267 exerciseDates_.push_back(XMLUtils::getChildValue(n, "ExerciseDate", true));
268 exerciseTypes_.push_back(XMLUtils::getChildValue(n, "Type", true));
269 exerciseValues_.push_back(parseReal(XMLUtils::getChildValue(n, "Value", true)));
270 }
271 }
272 }
273 // long short flag
274 optionLongShort_ = XMLUtils::getChildValue(swapNode, "OptionLongShort", true);
275 // underlying legs
276 swap_.clear();
277 vector<XMLNode*> nodes = XMLUtils::getChildrenNodes(swapNode, "LegData");
278 for (Size i = 0; i < nodes.size(); i++) {
279 LegData ld; // we do not allow ORE+ leg types anyway
280 ld.fromXML(nodes[i]);
281 swap_.push_back(ld);
282 }
283}
284
286 XMLNode* node = Trade::toXML(doc);
287 XMLNode* swapNode = doc.allocNode("FlexiSwapData");
288 XMLUtils::appendNode(node, swapNode);
289 // optionality given by lower notional bounds
290 if (!lowerNotionalBounds_.empty()) {
291 XMLUtils::addChildrenWithOptionalAttributes(doc, swapNode, "LowerNotionalBounds", "Notional",
293 }
294 // optionality given by exercise dates, types and values
295 if (!exerciseDates_.empty()) {
296 XMLNode* prepayNode = doc.allocNode("Prepayment");
297 XMLUtils::appendNode(swapNode, prepayNode);
298 if (!noticePeriod_.empty())
299 XMLUtils::addChild(doc, prepayNode, "NoticePeriod", noticePeriod_);
300 if (!noticeCalendar_.empty())
301 XMLUtils::addChild(doc, prepayNode, "NoticeCalendar", noticeCalendar_);
302 if (!noticeConvention_.empty())
303 XMLUtils::addChild(doc, prepayNode, "NoticeConvention", noticeConvention_);
304 XMLNode* optionsNode = doc.allocNode("PrepaymentOptions");
305 XMLUtils::appendNode(prepayNode, optionsNode);
306 for (Size i = 0; i < exerciseDates_.size(); ++i) {
307 XMLNode* exerciseNode = doc.allocNode("PrepaymentOption");
308 XMLUtils::appendNode(optionsNode, exerciseNode);
309 XMLUtils::addChild(doc, exerciseNode, "ExerciseDate", exerciseDates_.at(i));
310 XMLUtils::addChild(doc, exerciseNode, "Type", exerciseTypes_.at(i));
311 XMLUtils::addChild(doc, exerciseNode, "Value", exerciseValues_.at(i));
312 }
313 }
314 // long short option flag
315 XMLUtils::addChild(doc, swapNode, "OptionLongShort", optionLongShort_);
316 // underlying legs
317 for (Size i = 0; i < swap_.size(); i++)
318 XMLUtils::appendNode(swapNode, swap_[i].toXML(doc));
319 return node;
320}
321
322} // namespace data
323} // namespace ore
builder that returns an engine to price capped floored ibor legs
std::vector< double > lowerNotionalBounds_
Definition: flexiswap.hpp:90
std::vector< std::string > lowerNotionalBoundsDates_
Definition: flexiswap.hpp:91
std::string noticePeriod_
Definition: flexiswap.hpp:93
std::vector< double > exerciseValues_
Definition: flexiswap.hpp:95
std::vector< ore::data::LegData > swap_
Definition: flexiswap.hpp:88
virtual void fromXML(ore::data::XMLNode *node) override
Definition: flexiswap.cpp:246
std::string floatingIndex_
Store the name of the floating leg index.
Definition: flexiswap.hpp:100
std::string optionLongShort_
Definition: flexiswap.hpp:97
virtual ore::data::XMLNode * toXML(ore::data::XMLDocument &doc) const override
Definition: flexiswap.cpp:285
std::vector< std::string > exerciseDates_
Definition: flexiswap.hpp:94
std::string noticeConvention_
Definition: flexiswap.hpp:93
const std::vector< double > & lowerNotionalBounds() const
Definition: flexiswap.hpp:69
void build(const QuantLib::ext::shared_ptr< ore::data::EngineFactory > &) override
Definition: flexiswap.cpp:36
std::string noticeCalendar_
Definition: flexiswap.hpp:93
const std::string & optionLongShort() const
Definition: flexiswap.hpp:79
std::vector< std::string > exerciseTypes_
Definition: flexiswap.hpp:94
Serializable object holding leg data.
Definition: legdata.hpp:844
virtual void fromXML(XMLNode *node) override
Definition: legdata.cpp:759
string npvCurrency_
Definition: trade.hpp:201
std::vector< bool > legPayers_
Definition: trade.hpp:200
std::vector< string > legCurrencies_
Definition: trade.hpp:199
std::vector< QuantLib::Leg > legs_
Definition: trade.hpp:198
QuantLib::Real notional_
Definition: trade.hpp:202
virtual void fromXML(XMLNode *node) override
Definition: trade.cpp:34
void setSensitivityTemplate(const EngineBuilder &builder)
Definition: trade.cpp:295
virtual XMLNode * toXML(XMLDocument &doc) const override
Definition: trade.cpp:46
RequiredFixings requiredFixings_
Definition: trade.hpp:223
QuantLib::ext::shared_ptr< InstrumentWrapper > instrument_
Definition: trade.hpp:197
string notionalCurrency_
Definition: trade.hpp:203
std::map< std::string, boost::any > additionalData_
Definition: trade.hpp:224
Small XML Document wrapper class.
Definition: xmlutils.hpp:65
XMLNode * allocNode(const string &nodeName)
util functions that wrap rapidxml
Definition: xmlutils.cpp:132
static vector< XMLNode * > getChildrenNodes(XMLNode *node, const string &name)
Returns all the children with a given name.
Definition: xmlutils.cpp:428
static string getChildValue(XMLNode *node, const string &name, bool mandatory=false, const string &defaultValue=string())
Definition: xmlutils.cpp:277
static XMLNode * getChildNode(XMLNode *n, const string &name="")
Definition: xmlutils.cpp:387
static void addChildrenWithOptionalAttributes(XMLDocument &doc, XMLNode *n, const string &names, const string &name, const vector< T > &values, const string &attrName, const vector< string > &attrs)
Definition: xmlutils.cpp:542
static XMLNode * addChild(XMLDocument &doc, XMLNode *n, const string &name)
Definition: xmlutils.cpp:181
static void appendNode(XMLNode *parent, XMLNode *child)
Definition: xmlutils.cpp:406
Logic for calculating required fixing dates on legs.
Flexi-Swap data model and serialization.
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
Definition: parsers.cpp:51
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
Position::Type parsePositionType(const std::string &s)
Convert text to QuantLib::Position::Type.
Definition: parsers.cpp:404
BusinessDayConvention parseBusinessDayConvention(const string &s)
Convert text to QuantLib::BusinessDayConvention.
Definition: parsers.cpp:173
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
Definition: parsers.cpp:209
translates between QuantLib::Index::name() and ORE names
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
vector< T > buildScheduledVectorNormalised(const vector< T > &values, const vector< string > &dates, const Schedule &schedule, const T &defaultValue, const bool checkAllValuesAppearInResult=false)
Definition: legdata.hpp:1139
Real currentNotional(const Leg &leg)
Definition: legdata.cpp:2435
void addToRequiredFixings(const QuantLib::Leg &leg, const QuantLib::ext::shared_ptr< FixingDateGetter > &fixingDateGetter)
Schedule makeSchedule(const ScheduleDates &data)
Definition: schedule.cpp:263
Serializable Credit Default Swap.
Definition: namespaces.docs:23
vector< Real > strikes