Skip to content

Commit 73ef85e

Browse files
rolandlichtersjenkins
authored andcommitted
Resolve QPR-11618 american swaption pt 2 / 2
1 parent da02682 commit 73ef85e

20 files changed

Lines changed: 1074 additions & 825 deletions

File tree

Docs/UserGuide/tradedata/swaption.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ \subsubsection{Swaption}
66
component sub-node. These trade components are outlined in section \ref{ss:option_data} and section
77
\ref{ss:leg_data}.\\
88
\vspace{5mm}
9-
Supported swaption exercise styles are \emph{European} and \emph{Bermudan}. Swaptions of both exercise styles can have an arbitrary number of legs, with
9+
Supported swaption exercise styles are \emph{European}, \emph{Bermudan}, \emph{American}. Swaptions of all exercise styles can have an arbitrary number of legs, with
1010
each leg represented by a \lstinline!LegData! sub-node. Cross currency swaptions are not supported for either exercise style, i.e. the Currency element must
1111
have the same value for all \lstinline!LegData! sub-nodes of a swaption. There must be at least one full coupon period after the exercise date for European
1212
Swaptions, and after the last exercise date for Bermudan Swaptions. See Table \ref{tab:swaption_requirements} for further details on requirements for

Examples/Example_15/ExpectedOutput/npv.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#TradeId,TradeType,Maturity,MaturityTime,NPV,NpvCurrency,NPV(Base),BaseCurrency,Notional,NotionalCurrency,Notional(Base),NettingSet,CounterParty
2-
BERMUDAN_SWAPTION,Swaption,2038-10-01,22.652317,-3115851.065563,EUR,-3115851.065563,EUR,10000000.00,EUR,10000000.00,CPTY_A,CPTY_A
2+
BERMUDAN_SWAPTION,Swaption,2038-10-01,22.652317,-3115844.394193,EUR,-3115844.394193,EUR,10000000.00,EUR,10000000.00,CPTY_A,CPTY_A
33
BOND,Bond,2021-02-03,4.994783,12902649.282760,EUR,12902649.282760,EUR,10000000.00,EUR,10000000.00,CPTY_A,CPTY_A
44
Bond_Floating,Bond,2021-02-03,4.994783,10765048.489931,EUR,10765048.489931,EUR,10000000.00,EUR,10000000.00,,CPTY_C
55
CAP_EUR,CapFloor,2026-02-09,10.011221,-6881.951398,EUR,-6881.951398,EUR,1000000.00,EUR,1000000.00,CPTY_A,CPTY_A

Examples/Example_15/ExpectedOutput/scenario.csv

Lines changed: 382 additions & 382 deletions
Large diffs are not rendered by default.

Examples/Example_15/ExpectedOutput/sensitivity.csv

Lines changed: 80 additions & 80 deletions
Large diffs are not rendered by default.

Examples/Example_15/ExpectedOutput/stresstest.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#TradeId,ScenarioLabel,Base NPV,Scenario NPV,Sensitivity
2-
BERMUDAN_SWAPTION,parallel_rates,-3100941.24,-2043549.12,1057392.12
3-
BERMUDAN_SWAPTION,twist,-3100941.24,-3165028.92,-64087.68
2+
BERMUDAN_SWAPTION,parallel_rates,-3100934.56,-2043542.97,1057391.59
3+
BERMUDAN_SWAPTION,twist,-3100934.56,-3165020.59,-64086.03
44
BOND,parallel_rates,12902111.76,12614454.85,-287656.90
55
BOND,twist,12902111.76,12801562.04,-100549.71
66
Bond_Floating,parallel_rates,10765631.07,10511973.88,-253657.19

Examples/Example_15/ExpectedOutput/var.csv

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
#Portfolio,RiskClass,RiskType,Quantile_0.010000,Quantile_0.050000,Quantile_0.950000,Quantile_0.990000
2-
(all),(all),(all),-51235590.702637,-36273022.490219,35954247.490219,50916815.702637
2+
(all),(all),(all),-51235588.420345,-36273020.290624,35954249.290624,50916817.420345
33
PF1,(all),(all),-50726701.680738,-35873450.367032,35826125.367032,50679376.680738
4-
PF2,(all),(all),-6496762.740931,-4633322.329692,4361872.329692,6225312.740931
5-
(all),(all),DeltaGamma,-51228597.857258,-36266113.235621,35960753.235621,50923237.857258
4+
PF2,(all),(all),-6496758.482691,-4633318.732994,4361872.732994,6225312.482691
5+
(all),(all),DeltaGamma,-51228597.579535,-36266113.039256,35960753.039256,50923237.579535
66
PF1,(all),DeltaGamma,-50726701.680738,-35873450.367032,35826125.367032,50679376.680738
7-
PF2,(all),DeltaGamma,-6487763.642587,-4624994.545862,4366959.545862,6229728.642587
8-
(all),(all),Vega,-177437.215274,-127422.632151,114007.632151,164022.215274
9-
PF2,(all),Vega,-177437.215274,-127422.632151,114007.632151,164022.215274
10-
(all),InterestRate,(all),-36990503.836512,-26202194.865752,25875101.865752,36663410.836512
7+
PF2,(all),DeltaGamma,-6487761.420238,-4624992.974542,4366957.974542,6229726.420238
8+
(all),(all),Vega,-177433.848212,-127419.665564,114008.665564,164022.848212
9+
PF2,(all),Vega,-177433.848212,-127419.665564,114008.665564,164022.848212
10+
(all),InterestRate,(all),-36990501.444995,-26202192.588929,25875103.588929,36663412.444995
1111
PF1,InterestRate,(all),-36286970.228997,-25662864.686621,25621789.686621,36245895.228997
12-
PF2,InterestRate,(all),-6495322.849454,-4634438.067121,4348420.067121,6209304.849454
13-
(all),InterestRate,DeltaGamma,-36983211.482402,-26195003.828212,25881803.828212,36670011.482402
12+
PF2,InterestRate,(all),-6495318.588113,-4634434.468231,4348420.468231,6209304.588113
13+
(all),InterestRate,DeltaGamma,-36983211.097219,-26195003.555867,25881803.555867,36670011.097219
1414
PF1,InterestRate,DeltaGamma,-36286970.228997,-25662864.686621,25621789.686621,36245895.228997
15-
PF2,InterestRate,DeltaGamma,-6486370.984829,-4626073.665994,4353948.665994,6214245.984829
16-
(all),InterestRate,Vega,-166550.351677,-119795.045528,105902.045528,152657.351677
17-
PF2,InterestRate,Vega,-166550.351677,-119795.045528,105902.045528,152657.351677
15+
PF2,InterestRate,DeltaGamma,-6486368.759528,-4626072.092586,4353947.092586,6214243.759528
16+
(all),InterestRate,Vega,-166546.889318,-119792.011561,105903.011561,152657.889318
17+
PF2,InterestRate,Vega,-166546.889318,-119792.011561,105903.011561,152657.889318
1818
(all),Inflation,(all),-304149.096627,-215013.224331,215263.224331,304399.096627
1919
PF1,Inflation,(all),-304149.096627,-215013.224331,215263.224331,304399.096627
2020
(all),Inflation,DeltaGamma,-304149.096627,-215013.224331,215263.224331,304399.096627

Examples/Input/pricingengine.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@
6464
<Parameter name="nx">10</Parameter>
6565
</EngineParameters>
6666
</Product>
67+
<Product type="AmericanSwaption">
68+
<Model>LGM</Model>
69+
<ModelParameters>
70+
<Parameter name="Calibration">Bootstrap</Parameter>
71+
<Parameter name="CalibrationStrategy">CoterminalATM</Parameter>
72+
<Parameter name="Reversion">0.03</Parameter>
73+
<Parameter name="ReversionType">HullWhite</Parameter>
74+
<Parameter name="Volatility">0.01</Parameter>
75+
<Parameter name="VolatilityType">Hagan</Parameter>
76+
<!-- shift horizon as ratio of maturity time -->
77+
<Parameter name="ShiftHorizon">0.5</Parameter>
78+
<Parameter name="Tolerance">0.0001</Parameter>
79+
<Parameter name="ReferenceCalibrationGrid">400,3M</Parameter>
80+
<Parameter name="ExerciseTimeStepsPerYear">24</Parameter>
81+
</ModelParameters>
82+
<Engine>FD</Engine>
83+
<EngineParameters>
84+
<Parameter name="Scheme">Douglas</Parameter>
85+
<Parameter name="StateGridPoints">64</Parameter>
86+
<Parameter name="TimeStepsPerYear">24</Parameter>
87+
<Parameter name="MesherEpsilon">1E-4</Parameter>
88+
</EngineParameters>
89+
</Product>
6790
<Product type="CapFloor">
6891
<Model>IborCapModel</Model>
6992
<ModelParameters/>

OREData/ored/portfolio/builders/swaption.cpp

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (C) 2016-2022 Quaternion Risk Management Ltd
2+
Copyright (C) 2016 Quaternion Risk Management Ltd
33
All rights reserved.
44
55
This file is part of ORE, a free-software/open-source library
@@ -18,16 +18,16 @@
1818

1919
#include <ored/model/lgmbuilder.hpp>
2020
#include <ored/portfolio/builders/swaption.hpp>
21+
#include <ored/utilities/dategrid.hpp>
2122
#include <ored/utilities/parsers.hpp>
2223
#include <ored/utilities/to_string.hpp>
2324

2425
#include <ql/methods/montecarlo/lsmbasissystem.hpp>
2526
#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
26-
#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
2727

2828
#include <qle/methods/multipathgeneratorbase.hpp>
29-
#include <qle/pricingengines/numericlgmmultilegoptionengine.hpp>
3029
#include <qle/pricingengines/mcmultilegoptionengine.hpp>
30+
#include <qle/pricingengines/numericlgmmultilegoptionengine.hpp>
3131

3232
#include <set>
3333

@@ -77,17 +77,19 @@ boost::shared_ptr<PricingEngine> EuropeanSwaptionEngineBuilder::engineImpl(const
7777
}
7878
}
7979

80-
boost::shared_ptr<QuantExt::LGM> LGMBermudanSwaptionEngineBuilder::model(const string& id, const string& key,
81-
const std::vector<Date>& expiries,
82-
const Date& maturity,
83-
const std::vector<Real>& strikes) {
80+
boost::shared_ptr<QuantExt::LGM> LGMBermudanAmericanSwaptionEngineBuilder::model(const string& id, const string& key,
81+
const std::vector<Date>& expiries,
82+
const Date& maturity,
83+
const std::vector<Real>& strikes,
84+
const bool isAmerican) {
8485
boost::shared_ptr<IborIndex> index;
8586
std::string ccy = tryParseIborIndex(key, index) ? index->currency().code() : key;
8687

8788
DLOG("Get model data");
8889
auto calibration = parseCalibrationType(modelParameter("Calibration"));
8990
auto calibrationStrategy = parseCalibrationStrategy(modelParameter("CalibrationStrategy"));
90-
std::string referenceCalibrationGrid = modelParameter("ReferenceCalibrationGrid", {}, false, "");
91+
// required for american options to set up calibration basket
92+
std::string referenceCalibrationGrid = modelParameter("ReferenceCalibrationGrid", {}, isAmerican, "");
9193
Real lambda = parseReal(modelParameter("Reversion", {key, ccy}));
9294
vector<Real> sigma = parseListOfValues<Real>(modelParameter("Volatility"), &parseReal);
9395
vector<Real> sigmaTimes = parseListOfValues<Real>(modelParameter("VolatilityTimes", {}, false), &parseReal);
@@ -135,21 +137,45 @@ boost::shared_ptr<QuantExt::LGM> LGMBermudanSwaptionEngineBuilder::model(const s
135137
data->calibrationType() = calibration;
136138
data->shiftHorizon() = shiftHorizon;
137139

140+
std::vector<Date> effExpiries;
141+
std::vector<Real> effStrikes;
142+
if (!isAmerican) {
143+
effExpiries = expiries;
144+
effStrikes = strikes;
145+
} else {
146+
QL_REQUIRE(expiries.size() == 2 && strikes.size() == 2,
147+
"LGMBermudanAmericanSwaptionEngineBuilder::model(): expected 2 expiries and strikes for exercise "
148+
"style 'American', got "
149+
<< expiries.size() << " expiries and " << strikes.size() << " strikes.");
150+
// keep one calibration instrument per reference grid interval
151+
DateGrid grid(referenceCalibrationGrid);
152+
std::copy_if(grid.dates().begin(), grid.dates().end(), std::back_inserter(effExpiries),
153+
[&expiries](const Date& d) { return d >= expiries[0] && d < expiries[1]; });
154+
// simple linear interpolation of calibration strikes between endpoints, this can be refined obviously
155+
effStrikes.resize(effExpiries.size());
156+
Real t0 = Actual365Fixed().yearFraction(today, expiries[0]);
157+
Real t1 = Actual365Fixed().yearFraction(today, expiries[1]);
158+
for(Size i=0;i<effExpiries.size();++i) {
159+
Real t = Actual365Fixed().yearFraction(today, effExpiries[i]);
160+
effStrikes[i] = strikes[0] + (strikes[1] - strikes[0]) / (t1 - t0) * (t - t0);
161+
}
162+
}
163+
138164
if (calibrationStrategy == CalibrationStrategy::CoterminalATM ||
139165
calibrationStrategy == CalibrationStrategy::CoterminalDealStrike) {
140166
DLOG("Build LgmData for co-terminal specification");
141167
vector<string> expiryDates, termDates;
142-
for (Size i = 0; i < expiries.size(); ++i) {
143-
expiryDates.push_back(to_string(expiries[i]));
168+
for (Size i = 0; i < effExpiries.size(); ++i) {
169+
expiryDates.push_back(to_string(effExpiries[i]));
144170
termDates.push_back(to_string(maturity));
145171
}
146172
data->optionExpiries() = expiryDates;
147173
data->optionTerms() = termDates;
148174
data->optionStrikes().resize(expiryDates.size(), "ATM");
149175
if (calibrationStrategy == CalibrationStrategy::CoterminalDealStrike) {
150-
for (Size i = 0; i < expiries.size(); ++i) {
151-
if (strikes[i] != Null<Real>())
152-
data->optionStrikes()[i] = std::to_string(strikes[i]);
176+
for (Size i = 0; i < effExpiries.size(); ++i) {
177+
if (effStrikes[i] != Null<Real>())
178+
data->optionStrikes()[i] = std::to_string(effStrikes[i]);
153179
}
154180
}
155181
if (calibration == CalibrationType::Bootstrap) {
@@ -202,13 +228,13 @@ boost::shared_ptr<QuantExt::LGM> LGMBermudanSwaptionEngineBuilder::model(const s
202228
return model;
203229
}
204230

205-
boost::shared_ptr<PricingEngine> LGMGridBermudanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
206-
const std::vector<Date>& expiries,
207-
const Date& maturity,
208-
const std::vector<Real>& strikes) {
209-
DLOG("Building LGM Grid Bermudan Swaption engine for trade " << id);
231+
boost::shared_ptr<PricingEngine>
232+
LGMGridBermudanAmericanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
233+
const std::vector<Date>& expiries, const Date& maturity,
234+
const std::vector<Real>& strikes, const bool isAmerican) {
235+
DLOG("Building LGM Grid Bermudan/American Swaption engine for trade " << id);
210236

211-
boost::shared_ptr<QuantExt::LGM> lgm = model(id, key, expiries, maturity, strikes);
237+
boost::shared_ptr<QuantExt::LGM> lgm = model(id, key, expiries, maturity, strikes, isAmerican);
212238

213239
DLOG("Get engine data");
214240
Real sy = parseReal(engineParameter("sy"));
@@ -221,16 +247,17 @@ boost::shared_ptr<PricingEngine> LGMGridBermudanSwaptionEngineBuilder::engineImp
221247
boost::shared_ptr<IborIndex> index;
222248
std::string ccy = tryParseIborIndex(key, index) ? index->currency().code() : key;
223249
return boost::make_shared<QuantExt::NumericLgmMultiLegOptionEngine>(
224-
lgm, sy, ny, sx, nx, market_->discountCurve(ccy, configuration(MarketContext::pricing)));
250+
lgm, sy, ny, sx, nx, market_->discountCurve(ccy, configuration(MarketContext::pricing)),
251+
isAmerican ? parseInteger(modelParameter("ExerciseTimeStepsPerYear")) : 0);
225252
}
226253

227-
boost::shared_ptr<PricingEngine> LGMFDBermudanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
228-
const std::vector<Date>& expiries,
229-
const Date& maturity,
230-
const std::vector<Real>& strikes) {
231-
DLOG("Building LGM FD Bermudan Swaption engine for trade " << id);
254+
boost::shared_ptr<PricingEngine>
255+
LGMFDBermudanAmericanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
256+
const std::vector<Date>& expiries, const Date& maturity,
257+
const std::vector<Real>& strikes, const bool isAmerican) {
258+
DLOG("Building LGM FD Bermudan/American Swaption engine for trade " << id);
232259

233-
boost::shared_ptr<QuantExt::LGM> lgm = model(id, key, expiries, maturity, strikes);
260+
boost::shared_ptr<QuantExt::LGM> lgm = model(id, key, expiries, maturity, strikes, isAmerican);
234261

235262
DLOG("Get engine data");
236263
QuantLib::FdmSchemeDesc scheme = parseFdmSchemeDesc(engineParameter("Scheme"));
@@ -245,16 +272,17 @@ boost::shared_ptr<PricingEngine> LGMFDBermudanSwaptionEngineBuilder::engineImpl(
245272
std::string ccy = tryParseIborIndex(key, index) ? index->currency().code() : key;
246273
return boost::make_shared<QuantExt::NumericLgmMultiLegOptionEngine>(
247274
lgm, maxTime, scheme, stateGridPoints, timeStepsPerYear, mesherEpsilon,
248-
market_->discountCurve(ccy, configuration(MarketContext::pricing)));
275+
market_->discountCurve(ccy, configuration(MarketContext::pricing)),
276+
isAmerican ? parseInteger(modelParameter("ExerciseTimeStepsPerYear")) : 0);
249277
}
250278

251-
boost::shared_ptr<PricingEngine> LgmMcBermudanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
252-
const std::vector<Date>& expiries,
253-
const Date& maturity,
254-
const std::vector<Real>& strikes) {
255-
DLOG("Building MC Bermudan Swaption engine for trade " << id);
279+
boost::shared_ptr<PricingEngine>
280+
LgmMcBermudanAmericanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
281+
const std::vector<Date>& expiries, const Date& maturity,
282+
const std::vector<Real>& strikes, const bool isAmerican) {
283+
DLOG("Building MC Bermudan/American Swaption engine for trade " << id);
256284

257-
auto lgm = model(id, key, expiries, maturity, strikes);
285+
auto lgm = model(id, key, expiries, maturity, strikes, isAmerican);
258286

259287
// Build engine
260288
DLOG("Build engine (configuration " << configuration(MarketContext::pricing) << ")");
@@ -265,16 +293,17 @@ boost::shared_ptr<PricingEngine> LgmMcBermudanSwaptionEngineBuilder::engineImpl(
265293
std::vector<Date>(), std::vector<Size>());
266294
} // LgmMc engineImpl()
267295

268-
boost::shared_ptr<PricingEngine> LgmAmcBermudanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
269-
const std::vector<Date>& expiries,
270-
const Date& maturity,
271-
const std::vector<Real>& strikes) {
296+
boost::shared_ptr<PricingEngine>
297+
LgmAmcBermudanAmericanSwaptionEngineBuilder::engineImpl(const string& id, const string& key,
298+
const std::vector<Date>& expiries, const Date& maturity,
299+
const std::vector<Real>& strikes, const bool isAmerican) {
272300
boost::shared_ptr<IborIndex> index;
273301
std::string ccy = tryParseIborIndex(key, index) ? index->currency().code() : key;
274302
Currency curr = parseCurrency(ccy);
275-
DLOG("Building AMC Bermudan Swaption engine for key " << key << ", ccy " << ccy << " (from externally given CAM)");
303+
DLOG("Building AMC Bermudan/American Swaption engine for key " << key << ", ccy " << ccy
304+
<< " (from externally given CAM)");
276305

277-
QL_REQUIRE(cam_ != nullptr, "LgmCamBermudanSwaptionEngineBuilder::engineImpl: cam is null");
306+
QL_REQUIRE(cam_ != nullptr, "LgmCamBermudanAmericanSwaptionEngineBuilder::engineImpl: cam is null");
278307
Size currIdx = cam_->ccyIndex(curr);
279308
auto lgm = cam_->lgm(currIdx);
280309
std::vector<Size> modelIndex(1, cam_->pIdx(CrossAssetModel::AssetType::IR, currIdx));

0 commit comments

Comments
 (0)