Skip to content

Commit 46c98f4

Browse files
author
jenkins
committed
git subrepo pull (merge) ore
subrepo: subdir: "ore" merged: "49ad70d4c8" upstream: origin: "git@gitlab.acadiasoft.net:qs/ore.git" branch: "master" commit: "186cc746c6" git-subrepo: version: "0.4.6" origin: "https://github.com/ingydotnet/git-subrepo" commit: "73a0129"
2 parents 002df5e + 186cc74 commit 46c98f4

9 files changed

Lines changed: 82 additions & 68 deletions

File tree

Docker/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ DEBIAN_TAG=11.7
33
# it's recommended to include CMAKE_BUILD_TYPE and BOOST_VARIANT in QL_TAG, ORE_TAG, BOOST_TAG
44
# to distinguish a release build from a debug build
55

6-
QL_TAG=1.31.1_ddda9430
6+
QL_TAG=1.31.1_f8955cf0
77
ORE_TAG=latest
88

99
# debug or release
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
\subsubsection{CPI Swap}
22

3-
A CPI swap can be set up as a swap with trade type \emph{Swap}, with one leg of type {\tt CPI}. Listing \ref{lst:cpiswap}
4-
shows an example. The CPI leg contains an additional {\tt CPILegData} block. See \ref{ss:cpilegdata} for details on the
5-
CPI leg specification.
3+
A CPI inflation swap can be set up using the \emph{InflationSwap} trade type, with one leg of type {\tt CPI}. and the other leg(s) can be of any leg type. Listing \ref{lst:cpiinflationswap} shows an example. The CPI leg contains an additional {\tt CPILegData} block. See \ref{ss:cpilegdata} for details on the
4+
CPI leg specification.
5+
6+
Note that Cross Currency Inflation Swaps are supported, as the currencies on the legs of an \emph{InflationSwap} do not need to be the same.
7+
68

79
\begin{listing}[H]
810
%\hrule\medskip
911
\begin{minted}[fontsize=\footnotesize]{xml}
10-
<SwapData>
12+
<InflationSwapData>
1113
<LegData>
1214
<LegType>Floating</LegType>
1315
<Payer>true</Payer>
@@ -21,18 +23,19 @@ \subsubsection{CPI Swap}
2123
...
2224
</CPILegData>
2325
</LegData>
24-
</SwapData>
26+
</InflationSwapData>
2527
\end{minted}
26-
\caption{CPI Swap Data (using \emph{Swap} trade type)}
27-
\label{lst:cpiswap}
28+
\caption{CPI Swap Data (using \emph{InflationSwap} trade type)}
29+
\label{lst:cpiinflationswap}
2830
\end{listing}
2931

30-
Alternatively, a CPI swap can be set up using the \emph{InflationSwap} trade type, see Listing \ref{lst:cpiinflationswap}. The structure of the {\tt InflationSwapData} container is the same as for {\tt SwapData} above.
32+
33+
Alternatively, a CPI swap can be set up as a swap with trade type \emph{Swap}, with one leg of type {\tt CPI}, see listing \ref{lst:cpiswap}.
3134

3235
\begin{listing}[H]
3336
%\hrule\medskip
3437
\begin{minted}[fontsize=\footnotesize]{xml}
35-
<InflationSwapData>
38+
<SwapData>
3639
<LegData>
3740
<LegType>Floating</LegType>
3841
<Payer>true</Payer>
@@ -46,8 +49,9 @@ \subsubsection{CPI Swap}
4649
...
4750
</CPILegData>
4851
</LegData>
49-
</InflationSwapData>
52+
</SwapData>
5053
\end{minted}
51-
\caption{CPI Swap Data (using \emph{InflationSwap} trade type)}
52-
\label{lst:cpiinflationswap}
53-
\end{listing}
54+
\caption{CPI Swap Data (using \emph{Swap} trade type)}
55+
\label{lst:cpiswap}
56+
\end{listing}
57+
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#TradeId,TradeType,Maturity,MaturityTime,NPV,NpvCurrency,NPV(Base),BaseCurrency,Notional,NotionalCurrency,Notional(Base),NettingSet,CounterParty
22
Cap_USD_SOFR,Swap,2025-03-21,1.003324,0.000931,USD,0.000931,USD,100000000.00,USD,100000000.00,DUMMY_NS,DUMMY_CP
3-
Swaption_USD_SOFR,Swaption,2046-01-12,21.817022,250911.447892,USD,250911.447892,USD,100000000.00,USD,100000000.00,DUMMY_NS,DUMMY_CP
3+
Swaption_USD_SOFR,Swaption,2046-01-12,21.817022,250911.447904,USD,250911.447904,USD,100000000.00,USD,100000000.00,DUMMY_NS,DUMMY_CP

OREData/test/fittedbondcurve.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <ored/marketdata/fittedbondcurvehelpermarket.hpp>
2525
#include <ored/portfolio/enginefactory.hpp>
2626
#include <ored/portfolio/portfolio.hpp>
27+
#include <ored/portfolio/trade.hpp>
2728

2829
#include <ql/pricingengines/bond/discountingbondengine.hpp>
2930
#include <ql/termstructures/yield/bondhelpers.hpp>
@@ -38,37 +39,37 @@ BOOST_AUTO_TEST_SUITE(FittedBondCurveTests)
3839

3940
BOOST_AUTO_TEST_CASE(testCurveFromFixedRateBonds) {
4041

41-
#if QL_HEX_VERSION >= 0x01190000 || defined(QL_ORE_PATCH)
42+
4243
// will work in QL 1.19
4344
Date asof(6, April, 2020);
4445
Settings::instance().evaluationDate() = asof;
4546

4647
BOOST_TEST_MESSAGE("read pricing engine config");
47-
auto engineData = QuantLib::ext::make_shared<EngineData>();
48+
QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
4849
engineData->fromFile(TEST_INPUT_FILE("pricingengine.xml"));
4950

5051
BOOST_TEST_MESSAGE("read portfolio of bonds");
51-
auto portfolio = QuantLib::ext::make_shared<Portfolio>();
52-
portfolio->load(TEST_INPUT_FILE("portfolio1.xml"));
52+
QuantLib::ext::shared_ptr<Portfolio> portfolio = QuantLib::ext::make_shared<Portfolio>();
53+
portfolio->fromFile(TEST_INPUT_FILE("portfolio1.xml"));
5354

5455
BOOST_TEST_MESSAGE("build portfolio against FittedBondCurveHelperMarket");
55-
auto engineFactory =
56+
QuantLib::ext::shared_ptr < EngineFactory> engineFactory =
5657
QuantLib::ext::make_shared<EngineFactory>(engineData, QuantLib::ext::make_shared<FittedBondCurveHelperMarket>());
5758
portfolio->build(engineFactory);
5859

5960
BOOST_TEST_MESSAGE("set up bond helpers");
6061
std::vector<QuantLib::ext::shared_ptr<Bond>> bonds;
6162
std::vector<QuantLib::ext::shared_ptr<BondHelper>> helpers;
62-
for (auto const& t : portfolio->trades()) {
63-
auto bond = QuantLib::ext::dynamic_pointer_cast<Bond>(t->instrument()->qlInstrument());
63+
for (auto const& [tradeId, trade] : portfolio->trades()) {
64+
QuantLib::ext::shared_ptr<QuantLib::Bond> bond =
65+
QuantLib::ext::dynamic_pointer_cast<Bond>(trade->instrument()->qlInstrument());
6466
BOOST_REQUIRE(bond);
6567
bonds.push_back(bond);
6668
helpers.push_back(QuantLib::ext::make_shared<BondHelper>(Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(100.0)), bond));
6769
}
6870

6971
BOOST_TEST_MESSAGE("build fitted bond curve");
70-
Array guess(4);
71-
guess << 0.03, 0.03, 0.03, 0.5;
72+
Array guess({0.03, 0.03, 0.03, 0.5});
7273
NelsonSiegelFitting method((Array(), QuantLib::ext::shared_ptr<OptimizationMethod>(), Array()));
7374
auto curve =
7475
QuantLib::ext::make_shared<FittedBondDiscountCurve>(asof, helpers, Actual365Fixed(), method, 1E-10, 10000, guess);
@@ -82,7 +83,6 @@ BOOST_AUTO_TEST_CASE(testCurveFromFixedRateBonds) {
8283
<< " discount factor is " << curve->discount(b->maturityDate()));
8384
BOOST_CHECK_CLOSE(b->cleanPrice(), 100.0, 0.01); // 1bp tolerance in absolute price
8485
}
85-
#endif
8686
}
8787

8888
BOOST_AUTO_TEST_SUITE_END()

QuantExt/qle/termstructures/parametricvolatility.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,21 @@ Real ParametricVolatility::convert(const Real inputQuote, const MarketQuoteType
5757
Real forwardPremium;
5858
switch (marketModelType_) {
5959
case MarketModelType::Black76:
60-
if (inputMarketQuoteType_ == MarketQuoteType::Price) {
60+
if (inputMarketQuoteType == MarketQuoteType::Price) {
6161
forwardPremium = inputQuote / (discountCurve_.empty() ? 1.0 : discountCurve_->discount(timeToExpiry));
62-
} else if (inputMarketQuoteType_ == MarketQuoteType::NormalVolatility) {
62+
} else if (inputMarketQuoteType == MarketQuoteType::NormalVolatility) {
6363
forwardPremium =
6464
bachelierBlackFormula(inputOptionType, strike, forward, inputQuote * std::sqrt(timeToExpiry));
65-
} else if (inputMarketQuoteType_ == MarketQuoteType::ShiftedLognormalVolatility) {
65+
} else if (inputMarketQuoteType == MarketQuoteType::ShiftedLognormalVolatility) {
6666
if (strike < -inputLognormalShift)
6767
forwardPremium = inputOptionType == Option::Call ? forward - strike : 0.0;
68-
else
68+
else {
6969
forwardPremium = blackFormula(inputOptionType, strike, forward, inputQuote * std::sqrt(timeToExpiry),
70-
inputLognormalShift);
70+
1.0, inputLognormalShift);
71+
}
7172
} else {
7273
QL_FAIL("ParametricVolatility::convert(): MarketQuoteType ("
73-
<< static_cast<int>(inputMarketQuoteType_) << ") not handled. Internal error, contact dev.");
74+
<< static_cast<int>(inputMarketQuoteType) << ") not handled. Internal error, contact dev.");
7475
}
7576
break;
7677
default:

QuantExt/qle/termstructures/sabrparametricvolatility.cpp

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,22 @@ std::vector<std::pair<Real, bool>> SabrParametricVolatility::defaultModelParamet
123123
std::vector<Real> SabrParametricVolatility::direct(const std::vector<Real>& x, const Real forward,
124124
const Real lognormalShift) const {
125125
std::vector<Real> y(4);
126-
// beta as in QuantLib::SABRSpecs
127-
y[1] = std::fabs(x[1]) < std::sqrt(-std::log(eps1)) ? std::exp(-(x[1] * x[1])) : eps1;
128-
// max 0.02 normal vol equivalent
129-
Real fbeta = std::pow(forward + lognormalShift, y[1]);
130-
y[0] =
131-
(std::fabs(x[0]) < std::sqrt(-std::log(fbeta * eps1 / 0.02)) ? 0.02 * std::exp(-(x[0] * x[0])) / fbeta : eps1);
132-
// nu max 5.0
133-
y[2] = (std::fabs(x[2]) < std::sqrt(-std::log(2.0 * eps1)) ? 2.0 * std::exp(-(x[2] * x[2])) : eps1);
134-
// rho as in QuantLib::SABRSpecs
126+
y[1] = std::max(eps1, std::min(1.0 - eps1, std::exp(-(x[1] * x[1]))));
127+
Real fbeta = std::pow(std::max(forward + lognormalShift, eps1), y[1]);
128+
y[0] = std::max(eps1, std::exp(-(x[0] * x[0])) / fbeta * max_nvol_equiv);
129+
y[2] = std::max(eps1, std::exp(-(x[2] * x[2])) * max_nu);
135130
y[3] = std::fabs(x[3]) < 2.5 * M_PI ? eps2 * std::sin(x[3]) : eps2 * (x[3] > 0.0 ? 1.0 : (-1.0));
136131
return y;
137132
}
138133

139134
std::vector<Real> SabrParametricVolatility::inverse(const std::vector<Real>& y, const Real forward,
140135
const Real lognormalShift) const {
141136
std::vector<Real> x(4);
142-
x[1] = std::sqrt(-std::log(y[1]));
143-
Real fbeta = std::pow(forward + lognormalShift, y[1]);
144-
x[0] = std::sqrt(-std::log(y[0] * fbeta / 0.02));
145-
x[2] = std::sqrt(-std::log(y[2] / 2.0));
146-
x[3] = std::asin(y[3] / eps2);
137+
x[1] = std::sqrt(-std::log(std::min(1.0 - eps1, std::max(eps1, y[1]))));
138+
Real fbeta = std::pow(std::max(forward + lognormalShift, eps1), y[1]);
139+
x[0] = std::sqrt(-std::log(std::min(1.0 - eps1, std::max(eps1, y[0] * fbeta / max_nvol_equiv))));
140+
x[2] = std::sqrt(-std::log(std::min(1.0 - eps1, std::max(eps1, y[2] / max_nu))));
141+
x[3] = std::asin(std::max(-eps2, std::min(eps2, y[3])));
147142
return x;
148143
}
149144

@@ -267,9 +262,9 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
267262

268263
// if there are no free parameters, we just pass back the fixed parameters as the result
269264

270-
if(noFreeParams == 0) {
265+
if (noFreeParams == 0) {
271266
std::vector<Real> resultParams;
272-
for(auto const& p : params)
267+
for (auto const& p : params)
273268
resultParams.push_back(p.first);
274269
return std::make_tuple(resultParams, 0.0, 0);
275270
}
@@ -286,6 +281,7 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
286281
Real lognormalShift_;
287282
std::vector<Real> strikes_;
288283
std::vector<Real> marketQuotes_;
284+
Real refQuote_;
289285
std::function<std::vector<Real>(const std::vector<Real>&, const Real, const Real, const Real,
290286
const std::vector<Real>&)>
291287
evalSabr_;
@@ -310,23 +306,10 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
310306
Array result(strikes_.size());
311307
auto sabr = evalSabr(x);
312308
for (Size i = 0; i < strikes_.size(); ++i) {
313-
result[i] = (marketQuotes_[i] - sabr[i]);
309+
result[i] = (marketQuotes_[i] - sabr[i]) / refQuote_;
314310
}
315311
return result;
316312
}
317-
318-
Real normalizedError(const Array& x) const {
319-
auto sabr = evalSabr(x);
320-
Real maxQuote = *std::max_element(marketQuotes_.begin(), marketQuotes_.end());
321-
Real result = 0.0;
322-
for (Size i = 0; i < strikes_.size(); ++i) {
323-
/* we want a relative error measure, but can't do this point by point, because for far-ootm strikes
324-
* premiums are close to zero */
325-
result += std::pow((marketQuotes_[i] - sabr[i]) / maxQuote, 2.0);
326-
}
327-
result = std::sqrt(result / static_cast<Real>(strikes_.size()));
328-
return result;
329-
}
330313
};
331314

332315
/* build the target function and populate the members:
@@ -364,6 +347,8 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
364347
marketSmile.timeToExpiry, marketSmile.strikes[i], marketSmile.forward, preferredOutputQuoteType(),
365348
marketSmile.lognormalShift, boost::none));
366349
}
350+
// we use relative errors w.r.t. the max market quote, because far otm quotes are close to zero
351+
t.refQuote_ = *std::max_element(t.marketQuotes_.begin(), t.marketQuotes_.end());
367352

368353
// perform the calibration (this step might throw if all minimizations go wrong)
369354

@@ -406,7 +391,7 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
406391
continue;
407392
}
408393

409-
Real thisError = t.normalizedError(problem.currentValue());
394+
Real thisError = problem.functionValue();
410395
if (thisError < bestError) {
411396
bestError = thisError;
412397
for (Size i = 0, j = 0; i < bestResult.size(); ++i) {
@@ -427,6 +412,17 @@ SabrParametricVolatility::calibrateModelParameters(const MarketSmile& marketSmil
427412
return std::make_tuple(bestResult, bestError, ++attempt);
428413
}
429414

415+
namespace {
416+
void laplaceInterpolationWithErrorHandling(Matrix& m, const std::vector<Real>& x, const std::vector<Real>& y) {
417+
try {
418+
laplaceInterpolation(m, x, y, 1E-6, 100);
419+
} catch (const std::exception& e) {
420+
QL_FAIL("Error during laplaceInterpolation() in SabrParametricVolatility: "
421+
<< e.what() << ", this might be related to the numerical parameters relTol, maxIterMult. Contact dev.");
422+
}
423+
}
424+
} // namespace
425+
430426
void SabrParametricVolatility::calculate() {
431427

432428
// if no model parameters are given, we provide the default ones
@@ -515,10 +511,10 @@ void SabrParametricVolatility::calculate() {
515511

516512
// interpolate the null values
517513

518-
laplaceInterpolation(alpha_, timeToExpiries_, underlyingLengths_);
519-
laplaceInterpolation(beta_, timeToExpiries_, underlyingLengths_);
520-
laplaceInterpolation(nu_, timeToExpiries_, underlyingLengths_);
521-
laplaceInterpolation(rho_, timeToExpiries_, underlyingLengths_);
514+
laplaceInterpolationWithErrorHandling(alpha_, timeToExpiries_, underlyingLengths_);
515+
laplaceInterpolationWithErrorHandling(beta_, timeToExpiries_, underlyingLengths_);
516+
laplaceInterpolationWithErrorHandling(nu_, timeToExpiries_, underlyingLengths_);
517+
laplaceInterpolationWithErrorHandling(rho_, timeToExpiries_, underlyingLengths_);
522518

523519
// sanitize values produced by the the interpolation that are not allowed
524520

QuantExt/qle/termstructures/sabrparametricvolatility.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class SabrParametricVolatility final : public ParametricVolatility {
7474
private:
7575
static constexpr double eps1 = .0000001;
7676
static constexpr double eps2 = .9999;
77+
static constexpr double max_nvol_equiv = 0.02;
78+
static constexpr double max_nu = 2.0;
7779

7880
void calculate();
7981

QuantExt/qle/termstructures/swaptionsabrcube.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ SwaptionSabrCube::SwaptionSabrCube(
4646
registerWith(s);
4747
}
4848

49+
namespace {
50+
void laplaceInterpolationWithErrorHandling(Matrix& m, const std::vector<Real>& x, const std::vector<Real>& y) {
51+
try {
52+
laplaceInterpolation(m, x, y, 1e-6, 100);
53+
} catch (const std::exception& e) {
54+
QL_FAIL("Error during laplaceInterpolation() in SwaptionSabrCube: "
55+
<< e.what() << ", this might be related to the numerical parameters relTol, maxIterMult. Contact dev.");
56+
}
57+
}
58+
} // namespace
59+
4960
void SwaptionSabrCube::performCalculations() const {
5061

5162
SwaptionVolatilityCube::performCalculations();
@@ -81,7 +92,7 @@ void SwaptionSabrCube::performCalculations() const {
8192
}
8293

8394
for (auto& v : interpolatedVolSpreads) {
84-
laplaceInterpolation(v, allOptionTimes, allSwapLengths);
95+
laplaceInterpolationWithErrorHandling(v, allOptionTimes, allSwapLengths);
8596
}
8697

8798
// build market smiles on the grid

QuantLib

Submodule QuantLib updated from ddda943 to f8955cf

0 commit comments

Comments
 (0)