Skip to content

Commit 6d69065

Browse files
Roland Lichtersjenkins
authored andcommitted
Merge remote-tracking branch 'origin/master' into QPR-12456
2 parents b34efa6 + 64c4840 commit 6d69065

13 files changed

Lines changed: 133 additions & 51 deletions

File tree

OREAnalytics/orea/simm/crif.cpp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,27 +76,32 @@ void Crif::addSimmCrifRecord(const CrifRecord& record, bool aggregateDifferentAm
7676

7777
void Crif::insertCrifRecord(const CrifRecord& record, bool aggregateDifferentAmountCurrencies) {
7878

79-
auto it = records_.find(record);
80-
if (aggregateDifferentAmountCurrencies) {
81-
it = std::find_if(records_.begin(), records_.end(),
82-
[&record](const auto& x) { return CrifRecord::amountCcyEQCompare(x, record); });
83-
}
84-
if (it == records_.end()) {
85-
records_.insert(record);
79+
auto it = aggregateDifferentAmountCurrencies ? records_.end() : records_.find(record);
80+
auto itDiffAmountCcy = aggregateDifferentAmountCurrencies
81+
? diffAmountCurrenciesIndex_.find(record.getSimmAmountCcyKey())
82+
: diffAmountCurrenciesIndex_.end();
83+
84+
if (it == records_.end() && itDiffAmountCcy == diffAmountCurrenciesIndex_.end()) {
85+
auto recordIt = records_.insert(record);
86+
diffAmountCurrenciesIndex_[record.getSimmAmountCcyKey()] = &(*(recordIt.first));
8687
portfolioIds_.insert(record.portfolioId);
8788
nettingSetDetails_.insert(record.nettingSetDetails);
88-
} else {
89+
} else if (it != records_.end()) {
8990
updateAmountExistingRecord(it, record);
91+
} else {
92+
updateAmountExistingRecord(itDiffAmountCcy, record);
9093
}
9194
}
9295

9396
void Crif::addSimmParameterRecord(const CrifRecord& record) {
9497
auto it = records_.find(record);
9598
if (it == records_.end()) {
96-
records_.insert(record);
99+
CrifRecord newRecord = record;
100+
records_.insert(newRecord);
101+
diffAmountCurrenciesIndex_[record.getSimmAmountCcyKey()] = &newRecord;
97102
} else if (it->riskType == CrifRecord::RiskType::AddOnFixedAmount) {
98103
updateAmountExistingRecord(it, record);
99-
} else if (it->riskType == CrifRecord::RiskType::AddOnNotionalFactor &&
104+
} else if (it->riskType == CrifRecord::RiskType::AddOnNotionalFactor ||
100105
it->riskType == CrifRecord::RiskType::ProductClassMultiplier) {
101106
// Only log warning if the values are not the same. If they are, then there is no material discrepancy.
102107
if (record.amount != it->amount) {
@@ -127,6 +132,25 @@ void Crif::updateAmountExistingRecord(std::set<CrifRecord>::iterator& it, const
127132
DLOG("Updated net CRIF records: " << *it)
128133
}
129134

135+
void Crif::updateAmountExistingRecord(std::map<CrifRecord::SimmAmountCcyKey, const CrifRecord*>::iterator& it,
136+
const CrifRecord& record) {
137+
bool updated = false;
138+
if (record.hasAmountUsd()) {
139+
it->second->amountUsd += record.amountUsd;
140+
updated = true;
141+
}
142+
if (record.hasAmount() && record.hasAmountCcy() && it->second->amountCurrency == record.amountCurrency) {
143+
it->second->amount += record.amount;
144+
updated = true;
145+
}
146+
if (record.hasAmountResultCcy() && record.hasResultCcy() && it->second->resultCurrency == record.resultCurrency) {
147+
it->second->amountResultCcy += record.amountResultCcy;
148+
updated = true;
149+
}
150+
if (updated)
151+
DLOG("Updated net CRIF records: " << *(it->second))
152+
}
153+
130154
void Crif::addRecords(const Crif& crif, bool aggregateDifferentAmountCurrencies, bool sortFxVolQualifer) {
131155
for (const auto& r : crif.records_) {
132156
addRecord(r, aggregateDifferentAmountCurrencies, sortFxVolQualifer);

OREAnalytics/orea/simm/crif.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,12 @@ class Crif {
119119
void addSimmCrifRecord(const CrifRecord& record, bool aggregateDifferentAmountCurrencies = false, bool sortFxVolQualifer =true);
120120
void addSimmParameterRecord(const CrifRecord& record);
121121
void updateAmountExistingRecord(std::set<CrifRecord>::iterator& it, const CrifRecord& record);
122+
void updateAmountExistingRecord(std::map<CrifRecord::SimmAmountCcyKey, const CrifRecord*>::iterator& it, const CrifRecord& record);
122123

123124

124125
CrifType type_ = CrifType::Empty;
125126
std::set<CrifRecord> records_;
127+
std::map<CrifRecord::SimmAmountCcyKey, const CrifRecord*> diffAmountCurrenciesIndex_;
126128

127129
//SIMM members
128130
//! Set of portfolio IDs that have been loaded

OREAnalytics/orea/simm/crifrecord.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ struct CrifRecord {
125125
//! There are two entries for curvature risk in frtb, a up and down shift
126126
enum class CurvatureScenario { Empty, Up, Down };
127127

128+
// clang-format off
129+
// trade ID qualifier bucket label1 label2 collect regs post regs
130+
typedef std::tuple<std::string, NettingSetDetails, ProductClass, RiskType, std::string, std::string, std::string, std::string, std::string, std::string> SimmAmountCcyKey;
131+
// clang-format on
132+
128133
// required data
129134
std::string tradeId;
130135
std::string portfolioId;
@@ -286,6 +291,11 @@ struct CrifRecord {
286291
return value;
287292
}
288293

294+
const SimmAmountCcyKey getSimmAmountCcyKey() const {
295+
return std::make_tuple(tradeId, nettingSetDetails, productClass, riskType, qualifier, bucket, label1, label2,
296+
collectRegulations, postRegulations);
297+
}
298+
289299
//! Define how CRIF records are compared
290300
bool operator<(const CrifRecord& cr) const {
291301
if (type() == RecordType::FRTB || cr.type() == RecordType::FRTB) {

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
}

0 commit comments

Comments
 (0)