Skip to content

Commit f6cfeda

Browse files
mgronckijenkins
authored andcommitted
Resolve QPR-12398 handle commodity indices in scripted trade with GaussianCam model
1 parent 3046a82 commit f6cfeda

9 files changed

Lines changed: 82 additions & 38 deletions

File tree

OREData/ored/model/commodityschwartzmodelbuilder.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,13 @@ void CommoditySchwartzModelBuilder::performCalculations() const {
148148
return;
149149
}
150150

151+
// use identical start values for each calibration to ensure identical results for identical baskets
152+
model_->setParams(params_);
153+
151154
LOG("CommoditySchwartzModel for name " << data_->name() << " before calibration:"
152155
<< " sigma=" << parametrization_->sigmaParameter()
153156
<< " kappa=" << parametrization_->kappaParameter());
154157

155-
// use identical start values for each calibration to ensure identical results for identical baskets
156-
model_->setParams(params_);
157-
158158
model_->calibrate(optionBasket_, *data_->optimizationMethod(), data_->endCriteria(), data_->constraint(), weights, fix);
159159

160160
LOG("CommoditySchwartzModel for name " << data_->name() << " after calibration:"

OREData/ored/model/commodityschwartzmodeldata.hpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,20 @@ class CommoditySchwartzData {
110110
private:
111111
std::string name_;
112112
std::string ccy_;
113-
CalibrationType calibrationType_;
114-
bool calibrateSigma_;
115-
ParamType sigmaType_;
116-
Real sigmaValue_;
117-
bool calibrateKappa_;
118-
ParamType kappaType_;
119-
Real kappaValue_;
113+
CalibrationType calibrationType_ = CalibrationType::None;
114+
bool calibrateSigma_ = false;
115+
ParamType sigmaType_ = ParamType::Constant;
116+
Real sigmaValue_ = 0.0;
117+
bool calibrateKappa_ = false;
118+
ParamType kappaType_ = ParamType::Constant;
119+
Real kappaValue_ = 0.0;
120120
std::vector<std::string> optionExpiries_;
121121
std::vector<std::string> optionStrikes_;
122-
bool driftFreeState_;
122+
bool driftFreeState_ = false;
123123
QuantLib::ext::shared_ptr<OptimizationMethod> optimizationMethod_;
124124
EndCriteria endCriteria_;
125125
Constraint constraint_;
126-
BlackCalibrationHelper::CalibrationErrorType calibrationErrorType_;
126+
BlackCalibrationHelper::CalibrationErrorType calibrationErrorType_ = BlackCalibrationHelper::RelativePriceError;
127127
};
128128
} // namespace data
129129
} // namespace ore

OREData/ored/model/crossassetmodelbuilder.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,8 @@ void CrossAssetModelBuilder::buildModel() const {
485485
}
486486
auto builder = QuantLib::ext::dynamic_pointer_cast<CommoditySchwartzModelBuilder>(
487487
subBuilders_[CrossAssetModel::AssetType::COM][i]);
488+
if (dontCalibrate_)
489+
builder->freeze();
488490
csBuilder.push_back(builder);
489491
QuantLib::ext::shared_ptr<QuantExt::CommoditySchwartzParametrization> parametrization = builder->parametrization();
490492
comOptionBaskets_[i] = builder->optionBasket();

OREData/ored/portfolio/builders/scriptedtrade.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,8 +1301,8 @@ void ScriptedTradeEngineBuilder::buildGaussianCam(const std::string& id, const I
13011301

13021302
map<CorrelationKey, Handle<Quote>> camCorrelations;
13031303
for (auto const& c : tmpCorrelations) {
1304-
CorrelationFactor f_1 = parseCorrelationFactor(c.first.first);
1305-
CorrelationFactor f_2 = parseCorrelationFactor(c.first.second);
1304+
CorrelationFactor f_1 = parseCorrelationFactor(c.first.first, '#');
1305+
CorrelationFactor f_2 = parseCorrelationFactor(c.first.second, '#');
13061306
// update index for JY from 0 to 1 (i.e. to the factor driving the inf index ("fx") process)
13071307
// in all other cases the index 0 is fine, since there is only one driving factor always
13081308
if (infModelType_ == "JY") {
@@ -1321,11 +1321,11 @@ void ScriptedTradeEngineBuilder::buildGaussianCam(const std::string& id, const I
13211321
std::set<CorrelationFactor> allCorrRiskFactors;
13221322

13231323
for (auto const& m : modelIndices_)
1324-
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m).first));
1324+
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m).first, '#'));
13251325
for (auto const& m : modelIrIndices_)
1326-
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first));
1326+
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first, '#'));
13271327
for (auto const& m : modelInfIndices_)
1328-
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first));
1328+
allCorrRiskFactors.insert(parseCorrelationFactor(convertIndexToCamCorrelationEntry(m.first).first, '#'));
13291329
for (auto const& ccy : modelCcys_)
13301330
allCorrRiskFactors.insert({CrossAssetModel::AssetType::IR, ccy, 0});
13311331

@@ -1378,7 +1378,6 @@ void ScriptedTradeEngineBuilder::buildGaussianCam(const std::string& id, const I
13781378
std::vector<QuantLib::ext::shared_ptr<FxBsData>> fxConfigs;
13791379
std::vector<QuantLib::ext::shared_ptr<EqBsData>> eqConfigs;
13801380
std::vector<QuantLib::ext::shared_ptr<CommoditySchwartzData>> comConfigs;
1381-
// TODO: populate comConfigs
13821381

13831382
// calibration expiries and terms for IR, INF, FX, EQ parametrisations (this will only work for a fixed reference
13841383
// date, due to the way the cam builder and nested builders work, see ticket #940)
@@ -1597,8 +1596,27 @@ void ScriptedTradeEngineBuilder::buildGaussianCam(const std::string& id, const I
15971596
std::vector<QuantLib::ext::shared_ptr<CrLgmData>> crLgmConfigs;
15981597
std::vector<QuantLib::ext::shared_ptr<CrCirData>> crCirConfigs;
15991598

1600-
// COMM configs, not supported at this point
1601-
QL_REQUIRE(commIndices_.empty(), "GaussianCam model does not support commodity underlyings currently");
1599+
// COMM configs
1600+
for (auto const& comm : commIndices_) {
1601+
auto config = QuantLib::ext::make_shared<CommoditySchwartzData>();
1602+
config->currency() = getCommCcy(comm);
1603+
config->name() = comm.commName();
1604+
if (calibrationExpiries.empty() || zeroVolatility_) {
1605+
config->calibrationType() = CalibrationType::None;
1606+
config->calibrateSigma() = false;
1607+
config->sigmaParamType() = ParamType::Constant;
1608+
config->sigmaValue() = 0.0;
1609+
} else {
1610+
config->calibrationType() = CalibrationType::BestFit;
1611+
config->calibrateSigma() = true;
1612+
config->sigmaParamType() = ParamType::Constant;
1613+
config->sigmaValue() = 0.10; // start value for optimizer
1614+
config->optionExpiries() = calibrationExpiries;
1615+
config->optionStrikes() =
1616+
std::vector<std::string>(calibrationExpiries.size(), "ATMF"); // hardcoded ATMF calibration strike
1617+
}
1618+
comConfigs.push_back(config);
1619+
}
16021620

16031621
std::string configurationInCcy = configuration(MarketContext::irCalibration);
16041622
std::string configurationXois = configuration(MarketContext::pricing);

OREData/ored/scripting/models/gaussiancam.cpp

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,29 @@ void GaussianCam::performCalculations() const {
197197
}
198198

199199
indexPositionInProcess_.clear();
200+
eqIndexInCam_.resize(indices_.size());
201+
comIndexInCam_.resize(indices_.size());
202+
std::fill(eqIndexInCam_.begin(), eqIndexInCam_.end(), Null<Size>());
203+
std::fill(comIndexInCam_.begin(), comIndexInCam_.end(), Null<Size>());
200204
for (Size i = 0; i < indices_.size(); ++i) {
201205
if (indices_[i].isFx()) {
202206
// FX
203207
Size ccyIdx = cam_->ccyIndex(parseCurrency(indexCurrencies_[i]));
204208
QL_REQUIRE(ccyIdx > 0, "fx index '" << indices_[i] << "' has unexpected for ccy = base ccy");
205209
indexPositionInProcess_.push_back(cam_->pIdx(CrossAssetModel::AssetType::FX, ccyIdx - 1));
206-
eqIndexInCam_.push_back(Null<Size>());
207210
} else if (indices_[i].isEq()) {
208211
// EQ
209212
Size eqIdx = cam_->eqIndex(indices_[i].eq()->name());
210213
indexPositionInProcess_.push_back(cam_->pIdx(CrossAssetModel::AssetType::EQ, eqIdx));
211-
eqIndexInCam_.push_back(eqIdx);
214+
eqIndexInCam_[i] = eqIdx;
215+
} else if(indices_[i].isComm()) {
216+
// COM
217+
Size comIdx = cam_->comIndex(indices_[i].commName());
218+
indexPositionInProcess_.push_back(cam_->pIdx(CrossAssetModel::AssetType::COM, comIdx));
219+
comIndexInCam_[i] = comIdx;
212220
} else {
213-
QL_FAIL("index '" << indices_[i].name() << "' expected to be FX or EQ");
221+
QL_FAIL("GuassianCam::performCalculations(): index '" << indices_[i].name()
222+
<< "' expected to be FX, EQ, COMM");
214223
}
215224
}
216225

@@ -233,7 +242,7 @@ void GaussianCam::populatePathValues(const Size nSamples, std::map<Date, std::ve
233242

234243
// set reference date values, if there are no future simulation dates, we are done
235244

236-
// FX and EQ indcies
245+
// FX, EQ, COMM indcies
237246
for (Size k = 0; k < indices_.size(); ++k) {
238247
paths[referenceDate_][k].setAll(process->initialValues().at(indexPositionInProcess_[k]));
239248
}
@@ -351,7 +360,7 @@ void GaussianCam::populatePathValues(const Size nSamples, std::map<Date, std::ve
351360
}
352361
}
353362

354-
// FX and EQ indcies
363+
// FX, EQ, COMM indices
355364
std::vector<std::vector<RandomVariable*>> rvs(
356365
indices_.size(), std::vector<RandomVariable*>(effectiveSimulationDates_.size() - 1));
357366
auto date = effectiveSimulationDates_.begin();
@@ -417,8 +426,17 @@ void GaussianCam::populatePathValues(const Size nSamples, std::map<Date, std::ve
417426

418427
RandomVariable GaussianCam::getIndexValue(const Size indexNo, const Date& d, const Date& fwd) const {
419428
auto res = underlyingPaths_.at(d).at(indexNo);
420-
// compute forwarding factor
421-
if (fwd != Null<Date>()) {
429+
if (comIndexInCam_[indexNo] != Null<Size>()) {
430+
// handle com (TODO: performace optimization via vectorized version of com model)
431+
RandomVariable tmp(res.size());
432+
for (Size i = 0; i < tmp.size(); ++i) {
433+
tmp.set(i, cam_->comModel(comIndexInCam_[indexNo])
434+
->forwardPrice(timeFromReference(d), timeFromReference(fwd != Null<Date>() ? fwd : d),
435+
Array(1, std::log(res[i]))));
436+
}
437+
return tmp;
438+
} else if (fwd != Null<Date>()) {
439+
// handle fx, eq -> incorporate forwarding factor if applicable
422440
auto ccy = std::find(currencies_.begin(), currencies_.end(), indexCurrencies_[indexNo]);
423441
QL_REQUIRE(ccy != currencies_.end(), "GaussianCam::getIndexValue(): can not get currency for index #"
424442
<< indexNo << "(" << indices_.at(indexNo) << ")");
@@ -430,9 +448,11 @@ RandomVariable GaussianCam::getIndexValue(const Size indexNo, const Date& d, con
430448
res *= RandomVariable(size(), div->discount(fwd) / div->discount(d)) /
431449
getDiscount(std::distance(currencies_.begin(), ccy), d, fwd,
432450
cam_->eqbs(eqIndexInCam_[indexNo])->equityIrCurveToday());
451+
} else if (comIndexInCam_[indexNo] != Null<Size>()) {
452+
433453
} else {
434-
QL_FAIL("GaussianGam::getIndexValue(): did not recognise index #" << indexNo << "(" << indices_.at(indexNo)
435-
<< ")");
454+
QL_FAIL("GaussianGam::getIndexValue(): did not recognise index #" << indexNo << "("
455+
<< indices_.at(indexNo));
436456
}
437457
}
438458
return res;

OREData/ored/scripting/models/gaussiancam.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class GaussianCam : public ModelImpl, public AmcModel {
130130
mutable std::vector<Size> infIndexPositionInCam_; // maps inf index no to inf idx in cam
131131
mutable std::vector<Size> currencyPositionInCam_; // maps currency no to position in cam parametrizations
132132
mutable std::vector<Size> eqIndexInCam_; // maps index no to eq position in cam (or null, if not an eq index)
133+
mutable std::vector<Size> comIndexInCam_; // maps index no to com position in cam (or null, if not a com index)
133134
mutable bool conditionalExpectationUseIr_; // derived from input conditionalExpectationModelState
134135
mutable bool conditionalExpectationUseInf_; // derived from input conditionalExpectationModelState
135136
mutable bool conditionalExpectationUseAsset_; // derived from input conditionalExpectationModelState

OREData/ored/scripting/utilities.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,16 @@ ASTNodePtr parseScript(const std::string& code) {
138138
std::pair<std::string, Period> convertIndexToCamCorrelationEntry(const std::string& i) {
139139
IndexInfo info(i);
140140
if (info.isIr()) {
141-
return std::make_pair("IR:" + info.ir()->currency().code(), info.ir()->tenor());
141+
return std::make_pair("IR#" + info.ir()->currency().code(), info.ir()->tenor());
142142
} else if (info.isInf()) {
143-
return std::make_pair("INF:" + info.infName(), 0 * Days);
143+
return std::make_pair("INF#" + info.infName(), 0 * Days);
144144
} else if (info.isFx()) {
145-
return std::make_pair("FX:" + info.fx()->sourceCurrency().code() + info.fx()->targetCurrency().code(),
145+
return std::make_pair("FX#" + info.fx()->sourceCurrency().code() + info.fx()->targetCurrency().code(),
146146
0 * Days);
147147
} else if (info.isEq()) {
148-
return std::make_pair("EQ:" + info.eq()->name(), 0 * Days);
148+
return std::make_pair("EQ#" + info.eq()->name(), 0 * Days);
149+
} else if (info.isComm()) {
150+
return std::make_pair("COM#" + info.commName(), 0 * Days);
149151
} else {
150152
QL_FAIL("convertIndextoCamCorrelationEntry(): index '" << i << "' not recognised");
151153
}

OREData/ored/utilities/correlationmatrix.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,15 @@ ostream& operator<<(ostream& out, const CorrelationFactor& f) {
5555
return out << f.type << ":" << f.name << ":" << f.index;
5656
}
5757

58-
CorrelationFactor parseCorrelationFactor(const string& name) {
58+
CorrelationFactor parseCorrelationFactor(const string& name, const char separator) {
5959

60+
std::string sep(1, separator);
6061
vector<string> tokens;
61-
boost::split(tokens, name, boost::is_any_of(":"));
62+
boost::split(tokens, name, boost::is_any_of(sep));
6263

6364
QL_REQUIRE(tokens.size() == 2 || tokens.size() == 3,
64-
"parseCorrelationFactor(" << name
65-
<< "): expected 2 or 3 tokens separated by ':', e.g. 'IR:USD' or 'INF:UKRPI:0'");
65+
"parseCorrelationFactor(" << name << "): expected 2 or 3 tokens separated by separator ('" << sep
66+
<< "'), e.g. 'IR" << sep << "USD' or 'INF" << sep << "UKRPI" << sep << "0'");
6667

6768
return {parseCamAssetType(tokens[0]), tokens[1],
6869
static_cast<Size>(tokens.size() == 3 ? parseInteger(tokens[3]) : 0)};

OREData/ored/utilities/correlationmatrix.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ std::ostream& operator<<(std::ostream& out, const CorrelationFactor& f);
5454
\c type, \c name and \c index set to \c IR, \c EUR and \c 0 respectively. Note that the name is of the form
5555
\c type:name and the index is always set to 0 initially. The actual index is set separately.
5656
*/
57-
CorrelationFactor parseCorrelationFactor(const std::string& name);
57+
CorrelationFactor parseCorrelationFactor(const std::string& name, const char separator = ':');
5858

5959
/*! The key for storing the correlation data is the pair of factors.
6060
*/

0 commit comments

Comments
 (0)