Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fxkikobarrieroption.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
19#include <boost/make_shared.hpp>
32#include <ql/errors.hpp>
33#include <ql/exercise.hpp>
34#include <ql/instruments/barrieroption.hpp>
35#include <ql/instruments/barriertype.hpp>
36#include <ql/instruments/compositeinstrument.hpp>
37#include <ql/instruments/vanillaoption.hpp>
40
41using namespace QuantLib;
42
43namespace ore {
44namespace data {
45
46void FxKIKOBarrierOption::build(const QuantLib::ext::shared_ptr<EngineFactory>& engineFactory) {
47
48 // ISDA taxonomy
49 additionalData_["isdaAssetClass"] = string("Foreign Exchange");
50 additionalData_["isdaBaseProduct"] = string("Simple Exotic");
51 additionalData_["isdaSubProduct"] = string("Barrier");
52 additionalData_["isdaTransaction"] = string("");
53
54 additionalData_["boughAmount"] = boughtAmount_;
55 additionalData_["boughtCurrency"] = boughtCurrency_;
56 additionalData_["soldAmount"] = soldAmount_;
57 additionalData_["soldCurrency"] = soldCurrency_;
58
59 npvCurrency_ = soldCurrency_; // sold is the domestic
62
63 Date today = Settings::instance().evaluationDate();
64 const QuantLib::ext::shared_ptr<Market> market = engineFactory->market();
65 Date start = ore::data::parseDate(startDate_);
66 Calendar cal = ore::data::parseCalendar(calendar_);
67
68 // Only European Barrier supported for now
69 QL_REQUIRE(option_.style() == "European", "Option Style unknown: " << option_.style());
70 QL_REQUIRE(option_.exerciseDates().size() == 1, "Invalid number of exercise dates");
71 QL_REQUIRE(barriers_.size() == 2, "Invalid number of barriers");
72 QL_REQUIRE(barriers_[0].levels().size() == 1, "Invalid number of barrier levels");
73 QL_REQUIRE(barriers_[1].levels().size() == 1, "Invalid number of barrier levels");
74 QL_REQUIRE(barriers_[0].rebate() == 0, "rebates are not supported for KIKO options");
75 QL_REQUIRE(barriers_[1].rebate() == 0, "rebates are not supported for KIKO options");
76 QL_REQUIRE(barriers_[0].style().empty() || barriers_[0].style() == "American",
77 "only american barrier style supported");
78 QL_REQUIRE(barriers_[1].style().empty() || barriers_[1].style() == "American",
79 "only american barrier style supported");
80 QL_REQUIRE(tradeActions().empty(), "TradeActions not supported for FxBarrierOption");
81
82 Currency boughtCcy = parseCurrency(boughtCurrency_);
83 Currency soldCcy = parseCurrency(soldCurrency_);
84
85 // Payoff
86 Real strike = soldAmount_ / boughtAmount_;
87 Option::Type type = parseOptionType(option_.callPut());
88 QuantLib::ext::shared_ptr<StrikedTypePayoff> payoff(new PlainVanillaPayoff(type, strike));
89
90 // Exercise
91 Date expiryDate = parseDate(option_.exerciseDates().front());
92 maturity_ = std::max(expiryDate, option_.premiumData().latestPremiumDate());
93
94 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiryDate);
95
96 // build underlying vanilla option
97 QuantLib::ext::shared_ptr<Instrument> vanilla = QuantLib::ext::make_shared<VanillaOption>(payoff, exercise);
98
99 // we extract our KnockIn/KnockOut barrier data
100 Size knockInIndex;
101 Size knockOutIndex;
102
103 Barrier::Type knockInType;
104 Barrier::Type knockOutType;
105
106 Barrier::Type tmpBarrier = parseBarrierType(barriers_[0].type());
107 switch (tmpBarrier) {
108 case Barrier::Type::UpIn:
109 case Barrier::Type::DownIn:
110 knockInIndex = 0;
111 knockOutIndex = 1;
112 knockInType = tmpBarrier;
113 knockOutType = parseBarrierType(barriers_[1].type());
114 break;
115 case Barrier::Type::UpOut:
116 case Barrier::Type::DownOut:
117 knockInIndex = 1;
118 knockOutIndex = 0;
119 knockOutType = tmpBarrier;
120 knockInType = parseBarrierType(barriers_[1].type());
121 break;
122 default:
123 QL_FAIL("unsupported barrier type provided");
124 }
125 QL_REQUIRE(knockOutType == Barrier::Type::UpOut || knockOutType == Barrier::Type::DownOut,
126 "KIKO barrier requires one KnockOut barrier");
127 QL_REQUIRE(knockInType == Barrier::Type::UpIn || knockInType == Barrier::Type::DownIn,
128 "KIKO barrier requires one KnockIn barrier");
129
130 Real knockInLevel = barriers_[knockInIndex].levels()[0].value();
131 Real knockOutLevel = barriers_[knockOutIndex].levels()[0].value();
132
133 QL_REQUIRE(knockInLevel != knockOutLevel, "different levels must be provided");
134 // Check if the barrier has been triggered already
135 bool knockedIn = false;
136 bool knockedOut = false;
137 Handle<Quote> spot = market->fxSpot(boughtCurrency_ + soldCurrency_);
138
139 QuantLib::ext::shared_ptr<QuantExt::FxIndex> fxIndex;
140 if (!fxIndex_.empty()) {
141 auto fxi = market->fxIndex(fxIndex_);
142 if (!fxi.empty()) {
143 fxIndex = fxi.currentLink();
144 }
145 }
146
147
148 // checking fixings
149 if (startDate_ != "" && parseDate(startDate()) < today) {
150
151 QL_REQUIRE(fxIndex_ != "", "no fxIndex provided");
152 QL_REQUIRE(calendar_ != "", "no calendar provided");
153 bool inverted = false;
154 if (fxIndex->sourceCurrency() == soldCcy && fxIndex->targetCurrency() == boughtCcy) {
155 inverted = true;
156 } else {
157 QL_REQUIRE(fxIndex->sourceCurrency() == boughtCcy && fxIndex->targetCurrency() == soldCcy,
158 "Invalid FX Index " << fxIndex_ << "for bought " << boughtCcy << "and sold " << soldCcy);
159 }
160
161 Date d = start;
162 while (d < today && !knockedIn && !knockedOut) {
163 Real fixing = Null<Real>();
164 if (fxIndex->fixingCalendar().isBusinessDay(d)) {
165 fixing = fxIndex->pastFixing(d);
166 }
167 if (fixing == 0.0 || fixing == Null<Real>()) {
168 ALOG("Got invalid FX fixing for index " << fxIndex_ << " on " << d
169 << "Skipping this date, assuming no trigger");
170 } else {
171 if (inverted)
172 fixing = 1.0 / fixing;
173 ALOG("Checking FX fixing for index " << fxIndex_ << " on " << d << ", value " << fixing);
174
175 if (!knockedIn)
176 knockedIn = checkBarrier(fixing, knockInType, knockInLevel);
177 if (!knockedOut)
178 knockedOut = checkBarrier(fixing, knockOutType, knockOutLevel);
179 }
180 d = cal.advance(d, 1, Days);
181 }
182 }
183
184 // All possible instruments require an underlying vanilla option so we set this up
185 QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder("FxOption");
186 QL_REQUIRE(builder, "No FxOption builder found");
187 QuantLib::ext::shared_ptr<FxEuropeanOptionEngineBuilder> fxOptBuilder =
188 QuantLib::ext::dynamic_pointer_cast<FxEuropeanOptionEngineBuilder>(builder);
189 vanilla->setPricingEngine(fxOptBuilder->engine(boughtCcy, soldCcy, expiryDate));
190
191 // Add additional premium payments
192 Position::Type positionType = parsePositionType(option_.longShort());
193 Real bsInd = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);
194
195 std::vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
196 std::vector<Real> additionalMultipliers;
197 addPremiums(additionalInstruments, additionalMultipliers,
198 (positionType == Position::Long ? 1.0 : -1.0) * boughtAmount_, option_.premiumData(), -bsInd, soldCcy,
199 engineFactory, fxOptBuilder->configuration(MarketContext::pricing));
200
201 // we build a knock out option
202 QuantLib::ext::shared_ptr<Instrument> barrier =
203 QuantLib::ext::make_shared<QuantLib::BarrierOption>(knockOutType, knockOutLevel, 0, payoff, exercise);
204
205 builder = engineFactory->builder("FxBarrierOption");
206 QL_REQUIRE(builder, "No FxBarrierOption builder found");
207 QuantLib::ext::shared_ptr<FxBarrierOptionEngineBuilder> fxBarrierOptBuilder =
208 QuantLib::ext::dynamic_pointer_cast<FxBarrierOptionEngineBuilder>(builder);
209 barrier->setPricingEngine(fxBarrierOptBuilder->engine(boughtCcy, soldCcy, expiryDate, expiryDate));
210 setSensitivityTemplate(*fxBarrierOptBuilder);
211 Settlement::Type settleType = parseSettlementType(option_.settlement());
212
213 QuantLib::ext::shared_ptr<InstrumentWrapper> koInstrument =
214 QuantLib::ext::shared_ptr<InstrumentWrapper>(new SingleBarrierOptionWrapper(
215 barrier, positionType == Position::Long ? true : false, expiryDate,
216 settleType == Settlement::Physical ? true : false, vanilla, knockOutType, spot, knockOutLevel, 0, soldCcy,
217 start, fxIndex, cal, boughtAmount_, boughtAmount_, additionalInstruments, additionalMultipliers));
218
219 // if the trade is knocked in this is all we price
220 if (knockedIn || knockedOut) {
221 DLOG("This trade has been knocked-in, building a knock out option");
222 instrument_ = koInstrument;
223 // Otherwise we build a composite instrument
224 } else {
225
226 Handle<Quote> fx = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.0));
227 vector<QuantLib::ext::shared_ptr<InstrumentWrapper>> iw;
228 std::vector<Handle<Quote>> fxRates;
229
230 DLOG("adding a knock out option to our composite trade");
231 iw.push_back(koInstrument);
232 fxRates.push_back(fx);
233
234 // the second trade's additional instruments should have the opposite multipliers
235 std::vector<Real> flippedAdditionalMultipliers;
236 for (auto a : additionalMultipliers)
237 flippedAdditionalMultipliers.push_back(-1 * a);
238
239 // For an UpIn/UpOut or a DownIn/DownOut the kiko option is replicated as follows:
240 // V_kiko(L,U) = V_knockout(L) - V_knockout(U)
241 // where L is the knockout level, U is the knockIn level
242 // If the option touches U the trade is knocked in, if it then touches L it is knocked out.
243 if ((knockOutType == Barrier::Type::UpOut && knockInType == Barrier::Type::UpIn) ||
244 (knockOutType == Barrier::Type::DownOut && knockInType == Barrier::Type::DownIn)) {
245 DLOG("Barrier Types are UpIn/UpOut or DownIn/DownOut, we add a single Barrier Knock Out Option to our composite trade");
246
247 QuantLib::ext::shared_ptr<Instrument> barrier2 =
248 QuantLib::ext::make_shared<QuantLib::BarrierOption>(knockOutType, knockInLevel, 0, payoff, exercise);
249 barrier2->setPricingEngine(fxBarrierOptBuilder->engine(boughtCcy, soldCcy, expiryDate, expiryDate));
250 setSensitivityTemplate(*fxBarrierOptBuilder);
251 QuantLib::ext::shared_ptr<InstrumentWrapper> koInstrument2 =
252 QuantLib::ext::shared_ptr<InstrumentWrapper>(new SingleBarrierOptionWrapper(
253 barrier2, positionType == Position::Long ? false : true, expiryDate,
254 settleType == Settlement::Physical ? true : false, vanilla, knockOutType, spot, knockInLevel, 0,
255 soldCcy, start, fxIndex, cal, boughtAmount_, boughtAmount_, additionalInstruments, flippedAdditionalMultipliers));
256
257 iw.push_back(koInstrument2);
258 fxRates.push_back(fx);
259 // For all other cases the KIKO is replicated as follows:
260 // V_kiko(L,U) = V_knockout(L) - V_doubleknockout(L,U)
261 // where L is the knockout level, U is the knockIn level
262 // the Option is only exercised if L is never touched, and U has been touched
263 } else {
264 DLOG("We add a Double Barrier Knock Out Option to our composite trade");
265 builder = engineFactory->builder("FxDoubleBarrierOption");
266 QL_REQUIRE(builder, "No FxDoubleBarrierOption builder found");
267 QuantLib::ext::shared_ptr<FxDoubleBarrierOptionEngineBuilder> fxDoubleBarrierOptBuilder =
268 QuantLib::ext::dynamic_pointer_cast<FxDoubleBarrierOptionEngineBuilder>(builder);
269
270 QuantLib::ext::shared_ptr<Instrument> doubleBarrier = QuantLib::ext::make_shared<DoubleBarrierOption>(
271 DoubleBarrier::Type::KnockOut, std::min(knockInLevel, knockOutLevel),
272 std::max(knockInLevel, knockOutLevel), 0, payoff, exercise);
273 doubleBarrier->setPricingEngine(fxDoubleBarrierOptBuilder->engine(boughtCcy, soldCcy, expiryDate));
274 setSensitivityTemplate(*fxDoubleBarrierOptBuilder);
275
276 QuantLib::ext::shared_ptr<InstrumentWrapper> dkoInstrument =
277 QuantLib::ext::shared_ptr<InstrumentWrapper>(new DoubleBarrierOptionWrapper(
278 doubleBarrier, positionType == Position::Long ? false : true, expiryDate,
279 settleType == Settlement::Physical ? true : false, vanilla, DoubleBarrier::Type::KnockOut, spot,
280 std::min(knockInLevel, knockOutLevel), std::max(knockInLevel, knockOutLevel), 0, soldCcy,
281 start, fxIndex, cal, boughtAmount_, boughtAmount_, additionalInstruments, flippedAdditionalMultipliers));
282
283 iw.push_back(dkoInstrument);
284 fxRates.push_back(fx);
285 }
286 instrument_ = QuantLib::ext::shared_ptr<InstrumentWrapper>(new CompositeInstrumentWrapper(iw, fxRates, today));
287 }
288
289 if (start != Date()) {
290 for (Date d = start; d <= expiryDate; d = cal.advance(d, 1 * Days))
292 }
293}
294
295bool FxKIKOBarrierOption::checkBarrier(Real spot, Barrier::Type type, Real barrier) {
296 switch (type) {
297 case Barrier::DownIn:
298 case Barrier::DownOut:
299 return spot <= barrier;
300 case Barrier::UpIn:
301 case Barrier::UpOut:
302 return spot >= barrier;
303 default:
304 QL_FAIL("unknown barrier type " << type);
305 }
306}
307
309 Trade::fromXML(node);
310 XMLNode* fxNode = XMLUtils::getChildNode(node, "FxKIKOBarrierOptionData");
311 QL_REQUIRE(fxNode, "No FxKIKOBarrierOptionData Node");
312 option_.fromXML(XMLUtils::getChildNode(fxNode, "OptionData"));
313 XMLNode* barrierNode = XMLUtils::getChildNode(fxNode, "Barriers");
314 QL_REQUIRE(barrierNode, "No Barriers node");
315 auto barriers = XMLUtils::getChildrenNodes(barrierNode, "BarrierData");
316 for (auto const& b : barriers) {
317 barriers_.push_back(BarrierData());
318 barriers_.back().fromXML(b);
319 }
320 QL_REQUIRE(barriers_.size() == 2, "A KIKO barrier requires two BarrierData nodes");
321
322 startDate_ = XMLUtils::getChildValue(fxNode, "StartDate", false);
323 calendar_ = XMLUtils::getChildValue(fxNode, "Calendar", false);
324 fxIndex_ = XMLUtils::getChildValue(fxNode, "FXIndex", false);
325 boughtCurrency_ = XMLUtils::getChildValue(fxNode, "BoughtCurrency", true);
326 soldCurrency_ = XMLUtils::getChildValue(fxNode, "SoldCurrency", true);
327 boughtAmount_ = XMLUtils::getChildValueAsDouble(fxNode, "BoughtAmount", true);
328 soldAmount_ = XMLUtils::getChildValueAsDouble(fxNode, "SoldAmount", true);
329}
330
332 XMLNode* node = Trade::toXML(doc);
333 XMLNode* fxNode = doc.allocNode("FxKIKOBarrierOptionData");
334 XMLUtils::appendNode(node, fxNode);
335
336 XMLUtils::appendNode(fxNode, option_.toXML(doc));
337 XMLNode* barrierNode = doc.allocNode("Barriers");
338 for (auto& b : barriers_) {
339 XMLUtils::appendNode(barrierNode, b.toXML(doc));
340 }
341 XMLUtils::appendNode(fxNode, barrierNode);
342
343 XMLUtils::addChild(doc, fxNode, "StartDate", startDate_);
344 XMLUtils::addChild(doc, fxNode, "Calendar", calendar_);
345 XMLUtils::addChild(doc, fxNode, "FXIndex", fxIndex_);
346 XMLUtils::addChild(doc, fxNode, "BoughtCurrency", boughtCurrency_);
347 XMLUtils::addChild(doc, fxNode, "BoughtAmount", boughtAmount_);
348 XMLUtils::addChild(doc, fxNode, "SoldCurrency", soldCurrency_);
349 XMLUtils::addChild(doc, fxNode, "SoldAmount", soldAmount_);
350
351 return node;
352}
353
354} // namespace data
355} // namespace oreplus
Wrapper for option instruments, tracks whether option has been exercised or not.
Engine builder for FX Options.
Serializable obejct holding barrier data.
Definition: barrierdata.hpp:34
const vector< BarrierData > & barriers() const
virtual void fromXML(XMLNode *node) override
virtual XMLNode * toXML(XMLDocument &doc) const override
bool checkBarrier(Real spot, Barrier::Type type, Real level)
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
const string & callPut() const
Definition: optiondata.hpp:71
const string & longShort() const
Definition: optiondata.hpp:70
const string & style() const
Definition: optiondata.hpp:74
const string & settlement() const
Definition: optiondata.hpp:81
virtual void fromXML(XMLNode *node) override
Definition: optiondata.cpp:32
virtual XMLNode * toXML(XMLDocument &doc) const override
Definition: optiondata.cpp:86
const PremiumData & premiumData() const
Definition: optiondata.hpp:83
const vector< string > & exerciseDates() const
Definition: optiondata.hpp:76
QuantLib::Date latestPremiumDate() const
Definition: premiumdata.cpp:28
void addFixingDate(const QuantLib::Date &fixingDate, const std::string &indexName, const QuantLib::Date &payDate=Date::maxDate(), const bool alwaysAddIfPaysOnSettlement=false, const bool mandatoryFixing=true)
TradeActions & tradeActions()
Set the trade actions.
Definition: trade.hpp:126
string npvCurrency_
Definition: trade.hpp:201
QuantLib::Real notional_
Definition: trade.hpp:202
virtual void fromXML(XMLNode *node) override
Definition: trade.cpp:34
Date addPremiums(std::vector< QuantLib::ext::shared_ptr< Instrument > > &instruments, std::vector< Real > &multipliers, const Real tradeMultiplier, const PremiumData &premiumData, const Real premiumMultiplier, const Currency &tradeCurrency, const QuantLib::ext::shared_ptr< EngineFactory > &factory, const string &configuration)
Definition: trade.cpp:58
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 Real getChildValueAsDouble(XMLNode *node, const string &name, bool mandatory=false, double defaultValue=0.0)
Definition: xmlutils.cpp:286
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 XMLNode * addChild(XMLDocument &doc, XMLNode *n, const string &name)
Definition: xmlutils.cpp:181
static void appendNode(XMLNode *parent, XMLNode *child)
Definition: xmlutils.cpp:406
used to store multiple trade wrappers
Pricing Engine Factory.
FX Double Barrier Option data model and serialization.
FX Option data model and serialization.
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
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
Barrier::Type parseBarrierType(const std::string &s)
Convert std::string to QuantLib::BarrierType.
Definition: parsers.cpp:1042
Settlement::Type parseSettlementType(const std::string &s)
Convert text to QuantLib::Settlement::Type.
Definition: parsers.cpp:434
Option::Type parseOptionType(const std::string &s)
Convert text to QuantLib::Option::Type.
Definition: parsers.cpp:481
Map text representations to QuantLib/QuantExt types.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define ALOG(text)
Logging Macro (Level = Alert)
Definition: log.hpp:544
Size size(const ValueType &v)
Definition: value.cpp:145
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.