27#include <ql/math/matrixutilities/pseudosqrt.hpp>
28#include <ql/math/matrixutilities/symmetricschurdecomposition.hpp>
29#include <boost/regex.hpp>
38std::ostream&
operator<<(std::ostream& out,
const ext::shared_ptr<MarketRiskGroupBase>& riskGroup) {
39 return out << riskGroup->to_string();
42std::ostream&
operator<<(std::ostream& out,
const ext::shared_ptr<TradeGroupBase>& tradeGroup) {
43 return out << tradeGroup->to_string();
51 return riskClass_ == MarketRiskConfiguration::RiskClass::All &&
riskType_ == MarketRiskConfiguration::RiskType::All;
55 {MarketRiskConfiguration::RiskClass::All, 0}, {MarketRiskConfiguration::RiskClass::InterestRate, 1},
56 {MarketRiskConfiguration::RiskClass::Inflation, 2}, {MarketRiskConfiguration::RiskClass::Credit, 3},
57 {MarketRiskConfiguration::RiskClass::Equity, 4}, {MarketRiskConfiguration::RiskClass::FX, 5}};
60 {MarketRiskConfiguration::RiskType::All, 0},
61 {MarketRiskConfiguration::RiskType::DeltaGamma, 1},
62 {MarketRiskConfiguration::RiskType::Vega, 2},
63 {MarketRiskConfiguration::RiskType::BaseCorrelation, 3}};
66 const QuantLib::ext::shared_ptr<MarketRiskGroup>& rhs)
const {
94 auto rg = QuantLib::ext::dynamic_pointer_cast<MarketRiskGroup>(riskGroup);
95 QL_REQUIRE(rg,
"riskGroup must be of type MarketRiskGroup");
102 if (tgIdx_ == tradeGroups_.end())
110 auto tg = QuantLib::ext::dynamic_pointer_cast<TradeGroup>(tradeGroup);
111 QL_REQUIRE(tg,
"tradeGroup must be of type VarTradeGroup");
112 tradeGroups_.insert(tg);
118 if (multiThreadArgs_ && fullRevalArgs_ && !fullRevalArgs_->simMarket_)
122 if (fullRevalArgs_ && fullRevalArgs_->engineData_) {
123 fullRevalArgs_->engineData_ = QuantLib::ext::make_shared<EngineData>(*fullRevalArgs_->engineData_);
124 fullRevalArgs_->engineData_->globalParameters()[
"RunType"] = std::string(
"HistoricalPnL");
130 hisScenGen_ = QuantLib::ext::make_shared<HistoricalScenarioGeneratorWithFilteredDates>(
131 timePeriods(), hisScenGen_);
133 if (fullRevalArgs_ && fullRevalArgs_->simMarket_)
134 hisScenGen_->baseScenario() = fullRevalArgs_->simMarket_->baseScenario();
137 if (sensiArgs_ && hisScenGen_)
138 sensiPnlCalculator_ =
139 ext::make_shared<HistoricalSensiPnlCalculator>(hisScenGen_, sensiArgs_->sensitivityStream_);
141 if (fullRevalArgs_) {
142 LOG(
"Build the portfolio for full reval bt.");
144 if (!multiThreadArgs_) {
145 factory_ = QuantLib::ext::make_shared<EngineFactory>(fullRevalArgs_->engineData_, fullRevalArgs_->simMarket_,
146 map<MarketContext, string>(), fullRevalArgs_->referenceData_,
147 fullRevalArgs_->iborFallbackConfig_);
149 DLOG(
"Building the portfolio");
150 portfolio_->build(factory_,
"historical pnl generation");
151 DLOG(
"Portfolio built");
153 LOG(
"Creating the historical P&L generator (dryRun=" << std::boolalpha << fullRevalArgs_->dryRun_ <<
")");
154 ext::shared_ptr<NPVCube> cube = ext::make_shared<DoublePrecisionInMemoryCube>(
155 fullRevalArgs_->simMarket_->asofDate(), portfolio_->ids(),
156 vector<Date>(1, fullRevalArgs_->simMarket_->asofDate()), hisScenGen_->numScenarios());
158 histPnlGen_ = ext::make_shared<HistoricalPnlGenerator>(
159 calculationCurrency_, portfolio_, fullRevalArgs_->simMarket_,
160 hisScenGen_, cube, factory_->modelBuilders(), fullRevalArgs_->dryRun_);
162 histPnlGen_ = ext::make_shared<HistoricalPnlGenerator>(
163 calculationCurrency_, portfolio_, hisScenGen_,
164 fullRevalArgs_->engineData_,
165 multiThreadArgs_->nThreads_, multiThreadArgs_->today_, multiThreadArgs_->loader_,
166 multiThreadArgs_->curveConfigs_, multiThreadArgs_->todaysMarketParams_,
167 multiThreadArgs_->configuration_, multiThreadArgs_->simMarketData_, fullRevalArgs_->referenceData_,
168 fullRevalArgs_->iborFallbackConfig_, fullRevalArgs_->dryRun_, multiThreadArgs_->context_);
172 initialiseRiskGroups();
176 riskGroups_ = QuantLib::ext::make_shared<MarketRiskGroupContainer>();
177 tradeGroups_ = QuantLib::ext::make_shared<TradeGroupContainer>();
180 bool hasFilter =
false;
182 if (portfolioFilter_ !=
"") {
184 filter = boost::regex(portfolioFilter_);
185 LOG(
"Portfolio filter: " << portfolioFilter_);
189 string allStr =
"All";
190 auto tradeGroup = QuantLib::ext::make_shared<TradeGroup>(allStr);
191 tradeGroups_->add(tradeGroup);
193 QL_REQUIRE(portfolio_,
"No portfolio given");
194 for (
const auto& pId : portfolio_->portfolioIds()) {
195 if (breakdown_ && (!hasFilter || boost::regex_match(pId,
filter))) {
196 auto tradeGroupP = QuantLib::ext::make_shared<TradeGroup>(pId);
197 tradeGroups_->add(tradeGroupP);
201 for (
auto const& [tradeId, trade] : portfolio_->trades()) {
202 if (!hasFilter && trade->portfolioIds().size() == 0)
203 tradeIdGroups_[allStr].insert(make_pair(tradeId, pos));
205 for (
auto const& pId : trade->portfolioIds()) {
206 if (!hasFilter || boost::regex_match(pId,
filter)) {
207 tradeIdGroups_[allStr].insert(make_pair(tradeId, pos));
209 tradeIdGroups_[pId].insert(make_pair(tradeId, pos));
219 auto riskGroup = QuantLib::ext::make_shared<MarketRiskGroup>(rc, rt);
224 tradeGroups_->reset();
228 QL_REQUIRE(multiThreadArgs_ && fullRevalArgs_,
"MarketRiskBacktest: must be a multithreaded run");
231 auto initMarket = QuantLib::ext::make_shared<TodaysMarket>(
232 multiThreadArgs_->today_, multiThreadArgs_->todaysMarketParams_, multiThreadArgs_->loader_,
233 multiThreadArgs_->curveConfigs_,
true,
true,
false, fullRevalArgs_->referenceData_,
false,
234 fullRevalArgs_->iborFallbackConfig_);
236 fullRevalArgs_->simMarket_ = QuantLib::ext::make_shared<ore::analytics::ScenarioSimMarket>(
237 initMarket, multiThreadArgs_->simMarketData_, multiThreadArgs_->configuration_,
238 *multiThreadArgs_->curveConfigs_, *multiThreadArgs_->todaysMarketParams_,
true,
false,
false,
false,
239 fullRevalArgs_->iborFallbackConfig_);
244 histPnlGen_->unregisterAllProgressIndicators();
245 for (
auto const& i : this->progressIndicators())
246 histPnlGen_->registerProgressIndicator(i);
252 registerProgressIndicators();
254 LOG(
"Creating reports");
255 createReports(reports);
258 map<string, QuantLib::ext::shared_ptr<NPVCube>> sensiShiftCube;
259 ext::shared_ptr<SensitivityAggregator> sensiAgg;
262 sensiAgg = ext::make_shared<SensitivityAggregator>(tradeIdGroups_);
264 bool runDetailTrd = runTradeDetail(reports);
265 addPnlCalculators(reports);
269 Size currentRiskGroup = 0;
270 while (ext::shared_ptr<MarketRiskGroupBase> riskGroup =
riskGroups_->next()) {
271 LOG(
"[progress] Processing RiskGroup " << ++currentRiskGroup <<
" out of " <<
riskGroups_->size()
272 <<
") = " << riskGroup);
274 ext::shared_ptr<ScenarioFilter>
filter = createScenarioFilter(riskGroup);
280 updateFilter(riskGroup,
filter);
283 sensiAgg->aggregate(*sensiArgs_->sensitivityStream_,
filter);
287 if (generateCube(riskGroup)) {
288 histPnlGen_->generateCube(
filter);
289 if (fullRevalArgs_->writeCube_) {
291 writer.
write(histPnlGen_->cube(), {});
296 bool runSensiBased = sensiBased_;
299 tradeGroups_->reset();
300 while (ext::shared_ptr<TradeGroupBase> tradeGroup = tradeGroups_->next()) {
304 if (!runTradeRiskGroup(tradeGroup, riskGroup))
308 LOG(
"Start processing for RiskGroup: " << riskGroup <<
", TradeGroup: " << tradeGroup);
310 writePnl_ = tradeGroup->allLevel() && riskGroup->allLevel();
311 tradeIdIdxPairs_ = tradeIdGroups_.at(tradeGroupKey(tradeGroup));
314 transform(tradeIdIdxPairs_.begin(), tradeIdIdxPairs_.end(), back_inserter(tradeIds_),
315 [](
const pair<string, Size>& elem) {
return elem.first; });
317 set<SensitivityRecord> srs;
319 if (runSensiBased && sensiAgg) {
320 map<string, QuantLib::ext::shared_ptr<NPVCube>>::iterator scube;
322 auto tradeGpId = tradeGroupKey(tradeGroup);
323 srs = sensiAgg->sensitivities(tradeGpId);
326 sensiAgg->generateDeltaGamma(tradeGpId, deltas_, gammas_);
327 vector<RiskFactorKey> deltaKeys;
328 for (
const auto& [rfk, _] : deltas_)
329 deltaKeys.push_back(rfk);
331 string portfolio = portfolioId(tradeGroup);
337 scube = sensiShiftCube.find(portfolio);
338 if (sensiArgs_->shiftCalculator_) {
339 if (scube == sensiShiftCube.end()) {
340 DLOG(
"Populating Sensi Shifts for portfolio '" << portfolio <<
"'");
343 if (srs.size() == 0) {
344 ALOG(
"No senitivities found for RiskGroup = "
345 << riskGroup <<
" and tradeGroup " << tradeGroup <<
"; Skipping Sensi based PnL.");
346 runSensiBased =
false;
348 scube = sensiShiftCube.insert({portfolio, QuantLib::ext::shared_ptr<NPVCube>()}).first;
349 vector<RiskFactorKey> riskFactorKeys;
350 transform(deltas_.begin(), deltas_.end(), back_inserter(riskFactorKeys),
351 [](
const pair<RiskFactorKey, Real>& kv) {
return kv.first; });
352 sensiPnlCalculator_->populateSensiShifts(scube->second, riskFactorKeys,
353 sensiArgs_->shiftCalculator_);
359 ext::shared_ptr<CovarianceCalculator> covCalculator;
361 if (sensiArgs_->covarianceInput_.size() > 0) {
362 std::vector<bool> sensiKeyHasNonZeroVariance(deltaKeys.size(),
false);
365 covarianceMatrix_ = Matrix(deltaKeys.size(), deltaKeys.size(), 0.0);
366 Size unusedCovariance = 0;
367 for (
const auto& c : sensiArgs_->covarianceInput_) {
368 auto k1 = std::find(deltaKeys.begin(), deltaKeys.end(), c.first.first);
369 auto k2 = std::find(deltaKeys.begin(), deltaKeys.end(), c.first.second);
370 if (k1 != deltaKeys.end() && k2 != deltaKeys.end()) {
371 covarianceMatrix_(k1 - deltaKeys.begin(), k2 - deltaKeys.begin()) = c.second;
373 sensiKeyHasNonZeroVariance[k1 - deltaKeys.begin()] =
true;
377 DLOG(
"Found " << sensiArgs_->covarianceInput_.size() <<
" covariance matrix entries, "
379 <<
" do not match a portfolio sensitivity and will not be used.");
380 for (Size i = 0; i < sensiKeyHasNonZeroVariance.size(); ++i) {
381 if (!sensiKeyHasNonZeroVariance[i])
382 WLOG(
"Zero variance assigned to sensitivity key " << deltaKeys[i]);
386 DLOG(
"Covariance matrix has dimension " << deltaKeys.size() <<
" x " << deltaKeys.size());
387 if (salvage_ && !covarianceMatrix_.empty()) {
388 DLOG(
"Covariance matrix is not salvaged, check for positive semi-definiteness");
389 SymmetricSchurDecomposition ssd(covarianceMatrix_);
390 Real evMin = ssd.eigenvalues().back();
393 "ParametricVar: input covariance matrix is not positive semi-definite, smallest "
396 DLOG(
"Smallest eigenvalue is " << evMin);
397 salvage_ = QuantLib::ext::make_shared<QuantExt::NoCovarianceSalvage>();
400 covCalculator = ext::make_shared<CovarianceCalculator>(covariancePeriod());
402 includeDeltaMargin_ = includeDeltaMargin(riskGroup);
403 includeGammaMargin_ = includeGammaMargin(riskGroup);
406 if (covCalculator || pnlCalculators_.size() > 0) {
407 sensiPnlCalculator_->calculateSensiPnl(srs, deltaKeys, scube->second, pnlCalculators_,
408 covCalculator, tradeIds_, includeGammaMargin_,
409 includeDeltaMargin_, runDetailTrd);
411 covarianceMatrix_ = covCalculator->covariance();
413 handleSensiResults(reports, riskGroup, tradeGroup);
417 if (runFullReval(riskGroup))
418 handleFullRevalResults(reports, riskGroup, tradeGroup);
420 writeReports(reports, riskGroup, tradeGroup);
426 closeReports(reports);
430 QL_REQUIRE(boost::find_first(cubeFilename,
"FILTER"),
431 "cube file name '" << cubeFilename <<
"' must contain 'FILTER'");
432 fullRevalArgs_->writeCube_ =
true;
433 fullRevalArgs_->cubeDir_ = cubeDir;
434 fullRevalArgs_->cubeFilename_ = cubeFilename;
440 covarianceMatrix_ = Matrix();
441 tradeIdIdxPairs_.clear();
443 for (
const auto& p : pnlCalculators_)
448 for (
const auto& r : reports->reports())
452QuantLib::ext::shared_ptr<ore::analytics::ScenarioFilter>
454 auto rg = QuantLib::ext::dynamic_pointer_cast<MarketRiskGroup>(riskGroup);
455 QL_REQUIRE(rg,
"riskGroup must be of type MarketRiskGroup");
456 return QuantLib::ext::make_shared<RiskFilter>(rg->riskClass(), rg->riskType());
460 auto vtg = QuantLib::ext::dynamic_pointer_cast<TradeGroup>(tradeGroup);
461 QL_REQUIRE(vtg,
"TradeGroup of type TradeGroup required");
462 return vtg->portfolioId();
466 return portfolioId(tradeGroup);
Write an NPV cube to a human readable text file.
void write(const QuantLib::ext::shared_ptr< NPVCube > &cube, const std::map< std::string, std::string > &nettingSetMap, bool append=false)
Write a cube out to file.
static std::set< RiskClass > riskClasses(bool includeAll=false)
Give back a set containing the RiskClass values optionally excluding 'All'.
static std::set< RiskType > riskTypes(bool includeAll=false)
Give back a set containing the RiskType values optionally excluding 'All'.
std::set< QuantLib::ext::shared_ptr< MarketRiskGroup > >::const_iterator rgIdx_
QuantLib::ext::shared_ptr< MarketRiskGroupBase > next() override
void add(const QuantLib::ext::shared_ptr< MarketRiskGroupBase > &riskGroup) override
QuantLib::Size size() override
std::set< QuantLib::ext::shared_ptr< MarketRiskGroup >, CompRisk > riskGroups_
MarketRiskConfiguration::RiskType riskType_
std::string to_string() override
bool allLevel() const override
MarketRiskConfiguration::RiskClass riskClass_
virtual void initialise()
void enableCubeWrite(const std::string &cubeDir, const std::string &cubeFilename)
virtual void closeReports(const QuantLib::ext::shared_ptr< MarketRiskReport::Reports > &reports)
virtual void calculate(const QuantLib::ext::shared_ptr< Reports > &report)
virtual std::string tradeGroupKey(const QuantLib::ext::shared_ptr< TradeGroupBase > &tradeGroup) const
void initSimMarket()
Method to init simMarket_ for multi-threaded ctors.
virtual QuantLib::ext::shared_ptr< ScenarioFilter > createScenarioFilter(const QuantLib::ext::shared_ptr< MarketRiskGroupBase > &riskGroup)
virtual std::string portfolioId(const QuantLib::ext::shared_ptr< TradeGroupBase > &tradeGroup) const
virtual void initialiseRiskGroups()
Method for shared initialisation.
virtual void reset(const QuantLib::ext::shared_ptr< MarketRiskGroupBase > &riskGroup)
virtual void registerProgressIndicators()
void add(const QuantLib::ext::shared_ptr< TradeGroupBase > &tradeGroup) override
QuantLib::ext::shared_ptr< TradeGroupBase > next() override
SafeStack< Filter > filter
A Class to write a cube out to file.
A cube implementation that stores the cube in memory.
Base class for a market risk report.
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
std::string to_string(const LocationInfo &l)
Class for aggregating SensitivityRecords.
bool operator()(const QuantLib::ext::shared_ptr< MarketRiskGroup > &lhs, const QuantLib::ext::shared_ptr< MarketRiskGroup > &rhs) const
static std::map< MarketRiskConfiguration::RiskClass, QuantLib::Size > rcOrder
static std::map< MarketRiskConfiguration::RiskType, QuantLib::Size > rtOrder