Skip to content

Commit cd1e6c8

Browse files
pcaspersjenkins
authored andcommitted
Merge branch 'QPR-12637' into 'master'
QPR-12637: Refactor FX Digital Option, FX European Barrier Option Closes QPR-12637 See merge request qs/oreplus!2653
1 parent bdbabf9 commit cd1e6c8

7 files changed

Lines changed: 233 additions & 94 deletions

File tree

OREData/ored/portfolio/builders/fxdigitaloption.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,18 @@ class FxDigitalOptionEngineBuilder
6666

6767
//! Engine Builder for European cash-settled FX Digital Options
6868
class FxDigitalCSOptionEngineBuilder
69-
: public ore::data::CachingPricingEngineBuilder<string, const Currency&, const Currency&> {
69+
: public ore::data::CachingPricingEngineBuilder<string, const Currency&, const Currency&, const bool> {
7070
public:
7171
FxDigitalCSOptionEngineBuilder()
7272
: CachingEngineBuilder("GarmanKohlhagen", "AnalyticCashSettledEuropeanEngine", {"FxDigitalOptionEuropeanCS"}) {}
7373

7474
protected:
75-
virtual string keyImpl(const Currency& forCcy, const Currency& domCcy) override {
75+
virtual string keyImpl(const Currency& forCcy, const Currency& domCcy, const bool flipResults) override {
7676
return forCcy.code() + domCcy.code();
7777
}
7878

79-
virtual QuantLib::ext::shared_ptr<PricingEngine> engineImpl(const Currency& forCcy, const Currency& domCcy) override {
79+
virtual QuantLib::ext::shared_ptr<PricingEngine> engineImpl(const Currency& forCcy, const Currency& domCcy,
80+
const bool flipResults) override {
8081
string pair = forCcy.code() + domCcy.code();
8182

8283
QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> gbsp = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
@@ -86,7 +87,7 @@ class FxDigitalCSOptionEngineBuilder
8687
market_->discountCurve(domCcy.code(), configuration(ore::data::MarketContext::pricing)),
8788
market_->fxVol(pair, configuration(ore::data::MarketContext::pricing)));
8889

89-
return QuantLib::ext::make_shared<QuantExt::AnalyticCashSettledEuropeanEngine>(gbsp);
90+
return QuantLib::ext::make_shared<QuantExt::AnalyticCashSettledEuropeanEngine>(gbsp, flipResults);
9091
}
9192
};
9293

OREData/ored/portfolio/fxdigitaloption.cpp

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
#include <ql/instruments/compositeinstrument.hpp>
2626
#include <ql/instruments/payoffs.hpp>
2727
#include <ql/instruments/vanillaoption.hpp>
28+
#include <qle/instruments/cashsettledeuropeanoption.hpp>
2829

2930
using namespace QuantLib;
31+
using namespace QuantExt;
3032

3133
namespace ore {
3234
namespace data {
@@ -57,6 +59,7 @@ void FxDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>& engi
5759

5860
// Handle PayoffCurrency, we might have to flip the trade here
5961
Real strike = strike_;
62+
6063
bool flipResults = false;
6164
if (payoffCurrency_ == "") {
6265
DLOG("PayoffCurrency defaulting to " << domesticCurrency_ << " for FxDigitalOption " << id());
@@ -81,33 +84,84 @@ void FxDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>& engi
8184
// Exercise
8285
Date expiryDate = parseDate(option_.exerciseDates().front());
8386
QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiryDate);
84-
maturity_ = std::max(option_.premiumData().latestPremiumDate(), expiryDate);
87+
88+
Date paymentDate = expiryDate;
89+
const boost::optional<OptionPaymentData>& opd = option_.paymentData();
90+
91+
if (opd) {
92+
if (opd->rulesBased()) {
93+
const Calendar& cal = opd->calendar();
94+
QL_REQUIRE(cal != Calendar(), "Need a non-empty calendar for rules based payment date.");
95+
paymentDate = cal.advance(expiryDate, opd->lag(), Days, opd->convention());
96+
} else {
97+
const vector<Date>& dates = opd->dates();
98+
QL_REQUIRE(dates.size() == 1, "Need exactly one payment date for cash settled European option.");
99+
paymentDate = dates[0];
100+
}
101+
QL_REQUIRE(paymentDate >= expiryDate, "Payment date must be greater than or equal to expiry date.");
102+
}
103+
maturity_ = std::max(option_.premiumData().latestPremiumDate(), paymentDate);
85104
maturityType_ = maturity_ == expiryDate ? "Expiry Date" : "Option's Latest Premium Date";
86-
87-
// QL does not have an FXDigitalOption, so we add a vanilla one here and wrap
88-
// it in a composite.
89-
QuantLib::ext::shared_ptr<Instrument> vanilla = QuantLib::ext::make_shared<VanillaOption>(payoff, exercise);
90-
91-
// set pricing engines
92-
QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder(tradeType_);
93-
QL_REQUIRE(builder, "No builder found for " << tradeType_);
94-
QuantLib::ext::shared_ptr<FxDigitalOptionEngineBuilder> fxOptBuilder =
95-
QuantLib::ext::dynamic_pointer_cast<FxDigitalOptionEngineBuilder>(builder);
96-
vanilla->setPricingEngine(fxOptBuilder->engine(forCcy, domCcy, flipResults));
97-
setSensitivityTemplate(*fxOptBuilder);
98-
addProductModelEngine(*fxOptBuilder);
99-
100-
Position::Type positionType = parsePositionType(option_.longShort());
101-
Real bsInd = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);
102-
Real mult = bsInd;
103-
104-
std::vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
105-
std::vector<Real> additionalMultipliers;
106-
addPremiums(additionalInstruments, additionalMultipliers, mult, option_.premiumData(), -bsInd, domCcy,
107-
engineFactory, fxOptBuilder->configuration(MarketContext::pricing));
108-
109-
instrument_ = QuantLib::ext::shared_ptr<InstrumentWrapper>(
110-
new VanillaInstrument(vanilla, mult, additionalInstruments, additionalMultipliers));
105+
QuantLib::ext::shared_ptr<Instrument> vanilla;
106+
Real exercisePrice = Null<Real>();
107+
bool exercised = false;
108+
QuantLib::ext::shared_ptr<FxIndex> fxIndex;
109+
if (paymentDate == expiryDate) {
110+
const boost::optional<OptionExerciseData>& oed = option_.exerciseData();
111+
if (oed) {
112+
QL_REQUIRE(oed->date() == expiryDate, "The supplied exercise date ("
113+
<< io::iso_date(oed->date())
114+
<< ") should equal the option's expiry date ("
115+
<< io::iso_date(expiryDate) << ").");
116+
exercised = true;
117+
exercisePrice = oed->price();
118+
}
119+
if (option_.isAutomaticExercise()) {
120+
121+
fxIndex = buildFxIndex(fxIndex_, domCcy.code(), forCcy.code(), engineFactory->market(),
122+
engineFactory->configuration(MarketContext::pricing));
123+
requiredFixings_.addFixingDate(expiryDate, fxIndex_, paymentDate);
124+
}
125+
126+
vanilla = QuantLib::ext::make_shared<CashSettledEuropeanOption>(type, strike, payoffAmount_, expiryDate,
127+
paymentDate, option_.isAutomaticExercise(),
128+
fxIndex, exercised, exercisePrice);
129+
// set pricing engines
130+
QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder("FxDigitalOptionEuropeanCS");
131+
QL_REQUIRE(builder, "No builder found for " << tradeType_);
132+
QuantLib::ext::shared_ptr<FxDigitalCSOptionEngineBuilder> fxOptBuilder =
133+
QuantLib::ext::dynamic_pointer_cast<FxDigitalCSOptionEngineBuilder>(builder);
134+
vanilla->setPricingEngine(fxOptBuilder->engine(forCcy, domCcy, flipResults));
135+
setSensitivityTemplate(*fxOptBuilder);
136+
addProductModelEngine(*fxOptBuilder);
137+
Position::Type positionType = parsePositionType(option_.longShort());
138+
Real bsInd = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);
139+
Real mult = bsInd;
140+
std::vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
141+
std::vector<Real> additionalMultipliers;
142+
addPremiums(additionalInstruments, additionalMultipliers, mult, option_.premiumData(), -bsInd, domCcy,
143+
engineFactory, fxOptBuilder->configuration(MarketContext::pricing));
144+
instrument_ = QuantLib::ext::shared_ptr<InstrumentWrapper>(
145+
new VanillaInstrument(vanilla, mult, additionalInstruments, additionalMultipliers));
146+
} else {
147+
QuantLib::ext::shared_ptr<EngineBuilder> builder = engineFactory->builder("FxOptionForward");
148+
vanilla = QuantLib::ext::make_shared<QuantExt::VanillaForwardOption>(payoff, exercise, paymentDate, paymentDate);
149+
QuantLib::ext::shared_ptr<VanillaOptionEngineBuilder> fxOptBuilder =
150+
QuantLib::ext::dynamic_pointer_cast<VanillaOptionEngineBuilder>(builder);
151+
vanilla->setPricingEngine(fxOptBuilder->engine(
152+
forCcy, domCcy, envelope().additionalField("discount_curve", false, std::string()), paymentDate));
153+
setSensitivityTemplate(*fxOptBuilder);
154+
addProductModelEngine(*fxOptBuilder);
155+
Position::Type positionType = parsePositionType(option_.longShort());
156+
Real bsInd = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);
157+
Real mult = bsInd;
158+
std::vector<QuantLib::ext::shared_ptr<Instrument>> additionalInstruments;
159+
std::vector<Real> additionalMultipliers;
160+
addPremiums(additionalInstruments, additionalMultipliers, mult, option_.premiumData(), -bsInd, domCcy,
161+
engineFactory, fxOptBuilder->configuration(MarketContext::pricing));
162+
instrument_ = QuantLib::ext::shared_ptr<InstrumentWrapper>(
163+
new VanillaInstrument(vanilla, mult, additionalInstruments, additionalMultipliers));
164+
}
111165
}
112166

113167
void FxDigitalOption::fromXML(XMLNode* node) {
@@ -126,7 +180,6 @@ XMLNode* FxDigitalOption::toXML(XMLDocument& doc) const {
126180
XMLNode* node = Trade::toXML(doc);
127181
XMLNode* fxNode = doc.allocNode("FxDigitalOptionData");
128182
XMLUtils::appendNode(node, fxNode);
129-
130183
XMLUtils::appendNode(fxNode, option_.toXML(doc));
131184
XMLUtils::addChild(doc, fxNode, "Strike", strike_);
132185
XMLUtils::addChild(doc, fxNode, "PayoffCurrency", payoffCurrency_);

OREData/ored/portfolio/fxdigitaloption.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class FxDigitalOption : public FxSingleAssetDerivative {
7272
Real strike_;
7373
string payoffCurrency_;
7474
Real payoffAmount_;
75+
std::string fxIndex_;
7576
};
7677
} // namespace data
7778
} // namespace oreplus

0 commit comments

Comments
 (0)