Skip to content

Commit 48f6d64

Browse files
pcaspersjenkins
authored andcommitted
QPR-10088: Settlment Lag for Physically Settled FXOption
1 parent d67fccf commit 48f6d64

8 files changed

Lines changed: 112 additions & 24 deletions

File tree

OREData/ored/portfolio/builders/fxoption.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ using namespace QuantExt;
3535
boost::shared_ptr<PricingEngine> CamAmcFxOptionEngineBuilder::engineImpl(const string& assetName,
3636
const Currency& domCcy,
3737
const AssetClass& assetClassUnderlying,
38-
const Date& expiryDate) {
38+
const Date& expiryDate, const bool useFxSpot) {
3939

4040
QL_REQUIRE(assetClassUnderlying == AssetClass::FX, "FX Option required");
4141
Currency forCcy = parseCurrency(assetName);

OREData/ored/portfolio/builders/fxoption.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ class CamAmcFxOptionEngineBuilder : public VanillaOptionEngineBuilder {
8282
protected:
8383
// the pricing engine depends on the ccys only, so the base class key implementation will just do fine
8484
boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
85-
const AssetClass& assetClassUnderlying,
86-
const Date& expiryDate) override;
85+
const AssetClass& assetClassUnderlying, const Date& expiryDate, const bool useFxSpot) override;
8786

8887
private:
8988
const boost::shared_ptr<QuantExt::CrossAssetModel> cam_;

OREData/ored/portfolio/builders/vanillaoption.hpp

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ template <class T, typename... Args> class CachingOptionEngineBuilder : public C
5252
boost::shared_ptr<QuantLib::GeneralizedBlackScholesProcess> getBlackScholesProcess(const string& assetName,
5353
const Currency& ccy,
5454
const AssetClass& assetClassUnderlying,
55-
const std::vector<Time>& timePoints = {}) {
55+
const std::vector<Time>& timePoints = {},
56+
const bool useFxSpot = true) {
5657

5758
using VVTS = QuantExt::BlackMonotoneVarVolTermStructure;
5859
string config = this->configuration(ore::data::MarketContext::pricing);
@@ -74,9 +75,15 @@ template <class T, typename... Args> class CachingOptionEngineBuilder : public C
7475
vol = Handle<BlackVolTermStructure>(boost::make_shared<VVTS>(vol, timePoints));
7576
vol->enableExtrapolation();
7677
}
78+
if (useFxSpot) {
79+
return boost::make_shared<QuantLib::GeneralizedBlackScholesProcess>(
80+
this->market_->fxSpot(ccyPairCode, config), this->market_->discountCurve(assetName, config),
81+
this->market_->discountCurve(ccy.code(), config), vol);
82+
}
7783
return boost::make_shared<QuantLib::GeneralizedBlackScholesProcess>(
78-
this->market_->fxSpot(ccyPairCode, config), this->market_->discountCurve(assetName, config),
84+
this->market_->fxRate(ccyPairCode, config), this->market_->discountCurve(assetName, config),
7985
this->market_->discountCurve(ccy.code(), config), vol);
86+
8087

8188
} else if (assetClassUnderlying == AssetClass::COM) {
8289

@@ -109,25 +116,26 @@ template <class T, typename... Args> class CachingOptionEngineBuilder : public C
109116
\ingroup builders
110117
*/
111118
class VanillaOptionEngineBuilder
112-
: public CachingOptionEngineBuilder<string, const string&, const Currency&, const AssetClass&, const Date&> {
119+
: public CachingOptionEngineBuilder<string, const string&, const Currency&, const AssetClass&, const Date&, const bool> {
113120
public:
114121
VanillaOptionEngineBuilder(const string& model, const string& engine, const set<string>& tradeTypes,
115122
const AssetClass& assetClass, const Date& expiryDate)
116123
: CachingOptionEngineBuilder(model, engine, tradeTypes, assetClass), expiryDate_(expiryDate) {}
117124

118-
boost::shared_ptr<PricingEngine> engine(const string& assetName, const Currency& ccy, const Date& expiryDate) {
125+
boost::shared_ptr<PricingEngine> engine(const string& assetName, const Currency& ccy, const Date& expiryDate, const bool useFxSpot = true) {
119126
return CachingPricingEngineBuilder<string, const string&, const Currency&, const AssetClass&,
120-
const Date&>::engine(assetName, ccy, assetClass_, expiryDate);
127+
const Date&, const bool>::engine(assetName, ccy, assetClass_, expiryDate, useFxSpot);
121128
}
122129

123-
boost::shared_ptr<PricingEngine> engine(const Currency& ccy1, const Currency& ccy2, const Date& expiryDate) {
130+
boost::shared_ptr<PricingEngine> engine(const Currency& ccy1, const Currency& ccy2, const Date& expiryDate,
131+
const bool useFxSpot = true) {
124132
return CachingPricingEngineBuilder<string, const string&, const Currency&, const AssetClass&,
125-
const Date&>::engine(ccy1.code(), ccy2, assetClass_, expiryDate);
133+
const Date&, const bool>::engine(ccy1.code(), ccy2, assetClass_, expiryDate, useFxSpot);
126134
}
127135

128136
protected:
129137
virtual string keyImpl(const string& assetName, const Currency& ccy, const AssetClass& assetClassUnderlying,
130-
const Date& expiryDate) override {
138+
const Date& expiryDate, const bool useFxSpot) override {
131139
return assetName + "/" + ccy.code() + "/" + to_string(expiryDate);
132140
}
133141

@@ -147,7 +155,7 @@ class EuropeanOptionEngineBuilder : public VanillaOptionEngineBuilder {
147155
protected:
148156
virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
149157
const AssetClass& assetClassUnderlying,
150-
const Date& expiryDate) override {
158+
const Date& expiryDate, const bool useFxSpot) override {
151159
boost::shared_ptr<QuantLib::GeneralizedBlackScholesProcess> gbsp =
152160
getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
153161
Handle<YieldTermStructure> discountCurve =
@@ -168,8 +176,8 @@ class EuropeanForwardOptionEngineBuilder : public VanillaOptionEngineBuilder {
168176

169177
protected:
170178
virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
171-
const AssetClass& assetClassUnderlying,
172-
const Date& expiryDate) override {
179+
const AssetClass& assetClassUnderlying, const Date& expiryDate,
180+
const bool useFxSpot) override {
173181
boost::shared_ptr<QuantLib::GeneralizedBlackScholesProcess> gbsp =
174182
getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
175183
Handle<YieldTermStructure> discountCurve =
@@ -188,8 +196,8 @@ class EuropeanCSOptionEngineBuilder : public VanillaOptionEngineBuilder {
188196

189197
protected:
190198
virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
191-
const AssetClass& assetClassUnderlying,
192-
const Date& expiryDate) override {
199+
const AssetClass& assetClassUnderlying, const Date& expiryDate,
200+
const bool useFxSpot) override {
193201
boost::shared_ptr<QuantLib::GeneralizedBlackScholesProcess> gbsp =
194202
getBlackScholesProcess(assetName, ccy, assetClassUnderlying);
195203
Handle<YieldTermStructure> discountCurve =
@@ -223,7 +231,8 @@ class AmericanOptionFDEngineBuilder : public AmericanOptionEngineBuilder {
223231

224232
protected:
225233
virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
226-
const AssetClass& assetClass, const Date& expiryDate) override {
234+
const AssetClass& assetClass, const Date& expiryDate,
235+
const bool useFxSpot) override {
227236
// We follow the way FdBlackScholesBarrierEngine determines maturity for time grid generation
228237
Handle<YieldTermStructure> riskFreeRate =
229238
market_->discountCurve(ccy.code(), configuration(ore::data::MarketContext::pricing));
@@ -271,7 +280,8 @@ class AmericanOptionBAWEngineBuilder : public AmericanOptionEngineBuilder {
271280

272281
protected:
273282
virtual boost::shared_ptr<PricingEngine> engineImpl(const string& assetName, const Currency& ccy,
274-
const AssetClass& assetClass, const Date& expiryDate) override {
283+
const AssetClass& assetClass, const Date& expiryDate,
284+
const bool useFxSpot) override {
275285
boost::shared_ptr<QuantLib::GeneralizedBlackScholesProcess> gbsp = getBlackScholesProcess(assetName, ccy, assetClass);
276286
return boost::make_shared<QuantExt::BaroneAdesiWhaleyApproximationEngine>(gbsp);
277287
}

OREData/ored/portfolio/fxoption.cpp

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@
1818

1919
#include <boost/make_shared.hpp>
2020
#include <ored/portfolio/builders/fxoption.hpp>
21+
#include <ored/portfolio/builders/fxforward.hpp>
2122
#include <ored/portfolio/enginefactory.hpp>
2223
#include <ored/portfolio/fxoption.hpp>
24+
#include <ored/portfolio/fxforward.hpp>
2325
#include <ored/portfolio/legdata.hpp>
26+
#include <ored/portfolio/structuredtradewarning.hpp>
2427
#include <ored/utilities/log.hpp>
2528
#include <ored/utilities/marketdata.hpp>
29+
#include <ored/utilities/parsers.hpp>
30+
#include <qle/instruments/fxforward.hpp>
2631
#include <ql/errors.hpp>
2732
#include <ql/exercise.hpp>
2833
#include <ql/instruments/compositeinstrument.hpp>
@@ -35,6 +40,7 @@ namespace data {
3540

3641
void FxOption::build(const boost::shared_ptr<EngineFactory>& engineFactory) {
3742

43+
QuantLib::Date today = Settings::instance().evaluationDate();
3844
const boost::shared_ptr<Market>& market = engineFactory->market();
3945

4046
// If automatic exercise, check that we have a non-empty FX index string, parse it and attach curves from market.
@@ -54,8 +60,62 @@ void FxOption::build(const boost::shared_ptr<EngineFactory>& engineFactory) {
5460
indexName_ = fxIndex_;
5561
}
5662

57-
// Build the trade using the shared functionality in the base class.
58-
VanillaOptionTrade::build(engineFactory);
63+
const ext::optional<OptionPaymentData>& opd = option_.paymentData();
64+
expiryDate_ = parseDate(option_.exerciseDates().front());
65+
QuantLib::Date paymentDate = expiryDate_;
66+
if (option_.settlement() == "Physical" && opd) {
67+
if (opd->rulesBased()) {
68+
const Calendar& cal = opd->calendar();
69+
QL_REQUIRE(cal != Calendar(), "Need a non-empty calendar for rules based payment date.");
70+
paymentDate = cal.advance(expiryDate_, opd->lag(), Days, opd->convention());
71+
} else {
72+
if (opd->dates().size() > 1)
73+
ore::data::StructuredTradeWarningMessage(
74+
id(), tradeType(), "Trade build", "Found more than 1 payment date. The first one will be used.")
75+
.log();
76+
paymentDate = opd->dates().front();
77+
}
78+
79+
QL_REQUIRE(paymentDate >= expiryDate_, "Settlement date must be greater than or equal to expiry date.");
80+
81+
if (expiryDate_ <= today) {
82+
// building an fx forward instrument instead of the option
83+
Date fixingDate;
84+
Currency boughtCcy = parseCurrency(assetName_);
85+
Currency soldCcy = parseCurrency(currency_);
86+
ext::shared_ptr<QuantLib::Instrument> instrument =
87+
ext::make_shared<QuantExt::FxForward>(quantity_, boughtCcy, soldAmount(), soldCcy, maturity_, false,
88+
true, paymentDate, soldCcy, fixingDate);
89+
instrument_.reset(new VanillaInstrument(instrument));
90+
if (option_.exerciseData()) {
91+
if (option_.exerciseData()->date() <= expiryDate_) {
92+
// option is exercised
93+
// fxforward flow are flows from trade data
94+
legs_ = {{ext::make_shared<SimpleCashFlow>(quantity_, paymentDate)},
95+
{ext::make_shared<SimpleCashFlow>(soldAmount(), paymentDate)}};
96+
legCurrencies_ = {assetName_, currency_};
97+
legPayers_ = {false, true};
98+
} else
99+
QL_REQUIRE(option_.exerciseData()->date() <= expiryDate_,
100+
"Trade build error, exercise after option expiry is not allowed");
101+
} else {
102+
// option not exercised
103+
// set flows = 0
104+
legs_ = {};
105+
}
106+
forwardDate_ = paymentDate;
107+
paymentDate_ = paymentDate;
108+
VanillaOptionTrade::build(engineFactory);
109+
110+
} else {
111+
// Build the trade using the shared functionality in the base class.
112+
VanillaOptionTrade::build(engineFactory);
113+
}
114+
maturity_ = paymentDate;
115+
} else {
116+
VanillaOptionTrade::build(engineFactory);
117+
}
118+
59119

60120
additionalData_["boughtCurrency"] = assetName_;
61121
additionalData_["boughtAmount"] = quantity_;

OREData/ored/portfolio/vanillaoption.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ void VanillaOptionTrade::build(const boost::shared_ptr<ore::data::EngineFactory>
186186
} else {
187187
QL_REQUIRE(exerciseType == QuantLib::Exercise::Type::European, "Only European Forward Options currently supported");
188188
LOG("Built VanillaForwardOption for trade " << id());
189-
vanilla = boost::make_shared<QuantExt::VanillaForwardOption>(payoff, exercise, forwardDate_);
189+
vanilla = boost::make_shared<QuantExt::VanillaForwardOption>(payoff, exercise, forwardDate_, paymentDate_);
190190
if (assetClassUnderlying_ == AssetClass::COM)
191191
tradeTypeBuilder = tradeType_ + "Forward";
192192
}
@@ -212,8 +212,12 @@ void VanillaOptionTrade::build(const boost::shared_ptr<ore::data::EngineFactory>
212212
boost::dynamic_pointer_cast<VanillaOptionEngineBuilder>(builder);
213213
QL_REQUIRE(vanillaOptionBuilder != nullptr, "No engine builder found for trade type " << tradeTypeBuilder);
214214

215-
vanilla->setPricingEngine(vanillaOptionBuilder->engine(assetName_, ccy, expiryDate_));
216-
setSensitivityTemplate(*vanillaOptionBuilder);
215+
if (forwardDate_ != Date()) {
216+
vanilla->setPricingEngine(vanillaOptionBuilder->engine(assetName_, ccy, expiryDate_, false));
217+
} else {
218+
vanilla->setPricingEngine(vanillaOptionBuilder->engine(assetName_, ccy, expiryDate_, true));
219+
}
220+
setSensitivityTemplate(*vanillaOptionBuilder);
217221

218222
configuration = vanillaOptionBuilder->configuration(MarketContext::pricing);
219223
} else {

OREData/ored/portfolio/vanillaoption.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class VanillaOptionTrade : public Trade {
4949
TradeStrike strike() const { return strike_; }
5050
double quantity() const { return quantity_; }
5151
const QuantLib::Date forwardDate() const { return forwardDate_; }
52+
const QuantLib::Date paymentDate() const { return paymentDate_; }
5253
//@}
5354

5455
//! \name Serialisation
@@ -86,6 +87,9 @@ class VanillaOptionTrade : public Trade {
8687

8788
//! Store the (optional) forward date.
8889
QuantLib::Date forwardDate_;
90+
91+
//! Store the (optional) payment date.
92+
QuantLib::Date paymentDate_;
8993
};
9094
} // namespace data
9195
} // namespace ore

QuantExt/qle/instruments/vanillaforwardoption.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ namespace QuantExt {
3636
public:
3737
class arguments;
3838
class engine;
39+
VanillaForwardOption(const boost::shared_ptr<QuantLib::StrikedTypePayoff>& payoff,
40+
const boost::shared_ptr<QuantLib::Exercise>& exercise, const QuantLib::Date& forwardDate,
41+
const QuantLib::Date& paymentDate)
42+
: VanillaOption(payoff, exercise), forwardDate_(forwardDate), paymentDate_(paymentDate) {}
3943

4044
VanillaForwardOption(const boost::shared_ptr<QuantLib::StrikedTypePayoff>& payoff,
4145
const boost::shared_ptr<QuantLib::Exercise>& exercise, const QuantLib::Date& forwardDate)
@@ -45,13 +49,15 @@ namespace QuantExt {
4549

4650
private:
4751
QuantLib::Date forwardDate_;
52+
QuantLib::Date paymentDate_;
4853
};
4954

5055
//! %Arguments for Vanilla Forward Option calculation
5156
class VanillaForwardOption::arguments : public VanillaOption::arguments {
5257
public:
5358
arguments() {}
5459
QuantLib::Date forwardDate;
60+
QuantLib::Date paymentDate;
5561
};
5662

5763
inline void VanillaForwardOption::setupArguments(QuantLib::PricingEngine::arguments* args) const {
@@ -62,6 +68,7 @@ namespace QuantExt {
6268
QL_REQUIRE(arguments != 0, "wrong argument type");
6369

6470
arguments->forwardDate = forwardDate_;
71+
arguments->paymentDate = paymentDate_;
6572
}
6673

6774
//! base class for swaption engines

QuantExt/qle/pricingengines/analyticeuropeanforwardengine.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ namespace QuantExt {
9494
DiscountFactor dividendDiscount =
9595
process_->dividendYield()->discount(
9696
arguments_.forwardDate);
97-
DiscountFactor df = discountPtr->discount(arguments_.exercise->lastDate());
97+
DiscountFactor df;
98+
if (arguments_.paymentDate != Date())
99+
df = discountPtr->discount(arguments_.paymentDate);
100+
else
101+
df = discountPtr->discount(arguments_.exercise->lastDate());
98102
DiscountFactor riskFreeDiscountForFwdEstimation =
99103
process_->riskFreeRate()->discount(arguments_.forwardDate);
100104
Real spot = process_->stateVariable()->value();

0 commit comments

Comments
 (0)