Skip to content

Commit 88d3928

Browse files
damienbarkerGitlab CI
authored andcommitted
Merge branch 'feature/QPR-13626' into 'master'
QPR-13626 HistVaR: report historical pnl vectors on risk factor level Closes QPR-13626 See merge request qs/oreplus!3086
1 parent 0aaecf0 commit 88d3928

17 files changed

Lines changed: 303 additions & 105 deletions

Docs/UserGuide/examples/examples.tex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,9 @@ \subsubsection{Historical Simulation VaR}
615615
The analytic is specified as usual in {\tt ore.xml} with the following parameters:
616616
\begin{itemize}
617617
\item outputFile: csv file name of the resulting VaR report
618-
%\item breakdown: boolean, if true the VaR report will contain a breakdown by risk class and risk type, otherwise the report shows the portfolio-lvel VaR only.
619-
\item tradePnl: boolean, if true the VaR report will contain a breakdown by tradeID, risk class and risk type, otherwise the report shows the portfolio-lvel VaR only.
618+
%\item breakdown: boolean, if true the VaR report will contain a breakdown by risk class and risk type, otherwise the report shows the portfolio-level VaR only.
619+
\item tradePnl: boolean, if true the VaR report will contain a breakdown by tradeID, risk class and risk type, otherwise the report shows the portfolio-level VaR only.
620+
\item riskFactorBreakdown: boolean, if true the VaR report will contain a breakdown by risk factor.
620621
\item quantiles: comma separated list of quantiles to be reported
621622
\item portfolioFilter (optional): Only trades with {\tt portfolioId} equal to the provided filter name are processed, see {\tt portfolio.xml}; the entire portfolio is processed, if omitted
622623
\item historicalPeriod: comma-separated date list, an even number of ordered dates is required (d1, d2, d3, d4, ...), where each pair (d1-d2, d3-d4, ...) defines the start and end of historical observation periods used

Examples/MarketRisk/Input/ore_histsimvar.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<Parameter name="includeExpectedShortfall">Y</Parameter>
3939
<!--<Parameter name="breakdown">Y</Parameter>-->
4040
<Parameter name="tradePnl">N</Parameter>
41+
<!--<Parameter name="riskFactorBreakdown">Y</Parameter>-->
4142
<!--<Parameter name="portfolioFilter">PF1</Parameter>-->
4243
<Parameter name="outputFile">var.csv</Parameter>
4344
</Analytic>

OREAnalytics/orea/app/analytics/varanalytic.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ void ParametricVarAnalyticImpl::setVarReport(const QuantLib::ext::shared_ptr<ore
120120
if (inputs_->outputHistoricalScenarios())
121121
ReportWriter().writeHistoricalScenarios(
122122
scenarios->scenarioLoader(),
123-
QuantLib::ext::make_shared<CSVFileReport>(path(inputs_->resultsPath() / "backtest_histscenrios.csv").string(),
123+
QuantLib::ext::make_shared<CSVFileReport>(path(inputs_->resultsPath() / "backtest_histscenarios.csv").string(),
124124
',', false, inputs_->csvQuoteChar(), inputs_->reportNaString()));
125125

126126
auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(
@@ -146,6 +146,13 @@ void ParametricVarAnalyticImpl::setVarReport(const QuantLib::ext::shared_ptr<ore
146146
void HistoricalSimulationVarAnalyticImpl::setUpConfigurations() {
147147
VarAnalyticImpl::setUpConfigurations();
148148
analytic()->configurations().simMarketParams = inputs_->histVarSimMarketParams();
149+
// ORE Swig does not handle that yet
150+
// inputs_->loadParameter<bool>(riskFactorBreakdown_, "historicalSimulationVar", "riskFactorBreakdown", false,
151+
// std::function<bool(const string&)>(parseBool));
152+
riskFactorBreakdown_ = inputs_->riskFactorBreakdown();
153+
if(riskFactorBreakdown_){
154+
allowPartialScenarios_ = true;
155+
}
149156
}
150157

151158
void HistoricalSimulationVarAnalyticImpl::setVarReport(
@@ -163,18 +170,17 @@ void HistoricalSimulationVarAnalyticImpl::setVarReport(
163170
auto scenarios = buildHistoricalScenarioGenerator(
164171
inputs_->scenarioReader(), adjFactors, benchmarkVarPeriod, inputs_->mporCalendar(), inputs_->mporDays(),
165172
analytic()->configurations().simMarketParams, analytic()->configurations().todaysMarketParams,
166-
defaultReturnConfig, inputs_->mporOverlappingPeriods());
173+
defaultReturnConfig, inputs_->mporOverlappingPeriods(), riskFactorBreakdown_);
167174

168175
if (inputs_->outputHistoricalScenarios())
169176
ore::analytics::ReportWriter().writeHistoricalScenarios(
170177
scenarios->scenarioLoader(),
171178
QuantLib::ext::make_shared<CSVFileReport>(path(inputs_->resultsPath() / "var_histscenarios.csv").string(), ',',
172179
false, inputs_->csvQuoteChar(), inputs_->reportNaString()));
173-
174180
auto simMarket = QuantLib::ext::make_shared<ScenarioSimMarket>(
175181
analytic()->market(), analytic()->configurations().simMarketParams, Market::defaultConfiguration,
176182
*analytic()->configurations().curveConfig, *analytic()->configurations().todaysMarketParams, true, false, false,
177-
false, inputs_->iborFallbackConfig());
183+
allowPartialScenarios_, inputs_->iborFallbackConfig());
178184
simMarket->scenarioGenerator() = scenarios;
179185
scenarios->baseScenario() = simMarket->baseScenario();
180186

@@ -184,7 +190,7 @@ void HistoricalSimulationVarAnalyticImpl::setVarReport(
184190
varReport_ = ext::make_shared<HistoricalSimulationVarReport>(
185191
inputs_->baseCurrency(), analytic()->portfolio(), inputs_->portfolioFilter(), inputs_->varQuantiles(),
186192
benchmarkVarPeriod, scenarios, std::move(fullRevalArgs), inputs_->varBreakDown(),
187-
inputs_->includeExpectedShortfall(), inputs_->tradePnl(), inputs_->useAtParCouponsCurves(),
193+
inputs_->includeExpectedShortfall(), inputs_->tradePnl(), riskFactorBreakdown_, inputs_->useAtParCouponsCurves(),
188194
inputs_->useAtParCouponsTrades());
189195
}
190196

@@ -195,8 +201,14 @@ void HistoricalSimulationVarAnalyticImpl::addAdditionalReports(
195201
QuantLib::ext::make_shared<InMemoryReport>(inputs_->reportBufferSize());
196202

197203
reports->add(histPnLReport);
198-
199204
analytic()->addReport(label_, "historical_PnL", histPnLReport);
205+
206+
if(riskFactorBreakdown_){
207+
QuantLib::ext::shared_ptr<InMemoryReport> histPnLRFReport =
208+
QuantLib::ext::make_shared<InMemoryReport>(inputs_->reportBufferSize());
209+
reports->add(histPnLRFReport);
210+
analytic()->addReport(label_, "riskFactor_PnL", histPnLRFReport);
211+
}
200212
}
201213

202214
} // namespace analytics

OREAnalytics/orea/app/analytics/varanalytic.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class HistoricalSimulationVarAnalyticImpl : public VarAnalyticImpl {
9393
protected:
9494
void setVarReport(const QuantLib::ext::shared_ptr<ore::data::InMemoryLoader>& loader) override;
9595
void addAdditionalReports(const QuantLib::ext::shared_ptr<MarketRiskReport::Reports>& reports) override;
96+
bool riskFactorBreakdown_ = false;
97+
bool allowPartialScenarios_ = false;
9698
};
9799

98100
class HistoricalSimulationVarAnalytic : public VarAnalytic {

OREAnalytics/orea/app/inputparameters.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ class InputParameters {
266266
void setVarQuantiles(const std::string& s); // parse to vector<Real>
267267
void setVarBreakDown(bool b) { varBreakDown_ = b; }
268268
void setTradePnl(bool b) { tradePnL_ = b; }
269+
void setRiskFactorBreakdown(bool b) { riskFactorBreakdown_ = b; }
269270
void setIncludeExpectedShortfall(bool b) { includeExpectedShortfall_ = b; }
270271
void setPortfolioFilter(const std::string& s) { portfolioFilter_ = s; }
271272
void setVarMethod(const std::string& s) { varMethod_ = s; }
@@ -753,6 +754,7 @@ class InputParameters {
753754
const std::vector<Real>& varQuantiles() const { return varQuantiles_; }
754755
bool varBreakDown() const { return varBreakDown_; }
755756
bool tradePnl() const { return tradePnL_; }
757+
bool riskFactorBreakdown() const { return riskFactorBreakdown_; }
756758
bool includeExpectedShortfall() const { return includeExpectedShortfall_; }
757759
const std::string& portfolioFilter() const { return portfolioFilter_; }
758760
const std::string& varMethod() const { return varMethod_; }
@@ -1227,6 +1229,7 @@ class InputParameters {
12271229
std::vector<Real> varQuantiles_;
12281230
bool varBreakDown_ = false;
12291231
bool tradePnL_ = false;
1232+
bool riskFactorBreakdown_ = false;
12301233
bool includeExpectedShortfall_ = false;
12311234
std::string portfolioFilter_;
12321235
// Delta, DeltaGammaNormal, MonteCarlo, Cornish-Fisher, Saddlepoint

OREAnalytics/orea/app/oreapp.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,8 +1416,8 @@ void OREAppInputParameters::loadParameters() {
14161416
setVarBreakDown(parseBool(tmp));
14171417

14181418
tmp = params_->get("historicalSimulationVar", "tradePnl", false);
1419-
if (tmp != "")
1420-
setTradePnl(parseBool(tmp));
1419+
if (tmp != "")
1420+
setTradePnl(parseBool(tmp));
14211421

14221422
tmp = params_->get("historicalSimulationVar", "portfolioFilter", false);
14231423
if (tmp != "")
@@ -1426,6 +1426,11 @@ void OREAppInputParameters::loadParameters() {
14261426
tmp = params_->get("historicalSimulationVar", "outputHistoricalScenarios", false);
14271427
if (tmp != "")
14281428
setOutputHistoricalScenarios(parseBool(tmp));
1429+
1430+
tmp = params_->get("historicalSimulationVar", "riskFactorBreakdown", false);
1431+
if (tmp != "")
1432+
setRiskFactorBreakdown(parseBool(tmp));
1433+
14291434
}
14301435

14311436
/*************

OREAnalytics/orea/engine/historicalpnlgenerator.cpp

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <orea/engine/multithreadedvaluationengine.hpp>
2222
#include <orea/engine/valuationcalculator.hpp>
23+
#include <orea/engine/historicalsensipnlcalculator.hpp>
2324

2425
#include <orea/cube/jointnpvcube.hpp>
2526
#include <orea/cube/inmemorycube.hpp>
@@ -94,7 +95,7 @@ HistoricalPnlGenerator::HistoricalPnlGenerator(
9495
return {QuantLib::ext::make_shared<NPVCalculator>(baseCurrency)};
9596
}) {}
9697

97-
void HistoricalPnlGenerator::generateCube(const QuantLib::ext::shared_ptr<ScenarioFilter>& filter) {
98+
void HistoricalPnlGenerator::generateCube(const QuantLib::ext::shared_ptr<ScenarioFilter>& filter, const bool runRiskFactorBreakdown) {
9899

99100
DLOG("Filling historical P&L cube for " << portfolio_->size() << " trades and " << hisScenGen_->numScenarios()
100101
<< " scenarios.");
@@ -112,9 +113,32 @@ void HistoricalPnlGenerator::generateCube(const QuantLib::ext::shared_ptr<Scenar
112113
simMarket_->reset();
113114
simMarket_->scenarioGenerator() = hisScenGen_;
114115
hisScenGen_->baseScenario() = simMarket_->baseScenario();
116+
if(hisScenGen_->isRiskFactorBreakdown() && runRiskFactorBreakdown){
117+
//Run for RiskFactor PnL Breakdown
118+
ext::shared_ptr<Scenario> sc = hisScenGen_->baseScenario();
119+
std::vector<RiskFactorKey> deltaKeys = sc->keys();
120+
for(auto const& key: deltaKeys){
121+
// If we already have a non-empty shared_ptr for this key, skip rebuilding
122+
auto it = mapCube_.find(key);
123+
if (it != mapCube_.end() && it->second) {
124+
continue;
125+
}
126+
hisScenGen_->setCurrentKey(key);
127+
hisScenGen_->setIterator(0);
128+
ext::shared_ptr<NPVCube> newCube = ext::make_shared<InMemoryCubeOpt<double>>(simMarket_->asofDate(), portfolio_->ids(),
129+
vector<Date>(1, simMarket_->asofDate()), hisScenGen_->numScenarios());
130+
131+
valuationEngine_->buildCube(portfolio_, newCube, npvCalculator_(), ValuationEngine::ErrorPolicy::RemoveAll, true,
132+
nullptr, nullptr, {}, dryRun_);
133+
mapCube_[key] = newCube;
134+
hisScenGen_->reset();
135+
}
136+
//Run for Full report - Remove risk factor key - reset counter
137+
hisScenGen_->setCurrentKey(RiskFactorKey());
138+
hisScenGen_->setIterator(0);
139+
}
115140
valuationEngine_->buildCube(portfolio_, cube_, npvCalculator_(), ValuationEngine::ErrorPolicy::RemoveAll, true,
116141
nullptr, nullptr, {}, dryRun_);
117-
118142
} else {
119143
MultiThreadedValuationEngine engine(
120144
nThreads_, today_, QuantLib::ext::make_shared<ore::analytics::DateGrid>(), hisScenGen_->numScenarios(), loader_,
@@ -225,6 +249,54 @@ TradePnlStore HistoricalPnlGenerator::tradeLevelPnl(const set<pair<string, Size>
225249

226250
TradePnlStore HistoricalPnlGenerator::tradeLevelPnl() const { return tradeLevelPnl(timePeriod()); }
227251

252+
using RiskFactorPnLSeries = HistoricalPnlGenerator::RiskFactorPnLSeries;
253+
RiskFactorPnLSeries HistoricalPnlGenerator::riskFactorLevelPnlSeries(const ore::data::TimePeriod& period) const {
254+
RiskFactorPnLSeries series;
255+
if (mapCube_.empty()) {
256+
DLOG("riskFactorLevelPnlSeries: mapCube_ is empty; returning empty result");
257+
return series;
258+
}
259+
260+
// Build per-scenario maps for scenarios within the period
261+
// Determine how many scenarios we have from any cube in the map
262+
Size samples = mapCube_.begin()->second->samples();
263+
series.resize(samples);
264+
265+
Size dateIdx = 0; // risk factor cubes have a single date at asof
266+
for (Size s = 0; s < samples; ++s) {
267+
Date start = hisScenGen_->startDates()[s];
268+
Date end = hisScenGen_->endDates()[s];
269+
if (!(period.contains(start) && period.contains(end))) {
270+
continue; // leave this scenario's map empty
271+
}
272+
// Aggregate PnL across trades and collapse duplicates by risk factor name
273+
std::unordered_map<std::string, std::pair<RiskFactorKey, Real>> byName;
274+
for (const auto& kv : mapCube_) {
275+
const RiskFactorKey& rfKey = kv.first;
276+
const ext::shared_ptr<NPVCube>& rfCube = kv.second;
277+
Real pnl = 0.0;
278+
Size tradeCount = rfCube->numIds();
279+
for (Size i = 0; i < tradeCount; ++i) {
280+
pnl += rfCube->get(i, dateIdx, s) - rfCube->getT0(i);
281+
}
282+
if (pnl == 0.0)
283+
continue;
284+
auto it = byName.find(rfKey.name);
285+
if (it == byName.end()) {
286+
byName.emplace(rfKey.name, std::make_pair(rfKey, pnl));
287+
} else {
288+
it->second.second += pnl;
289+
}
290+
}
291+
// Write one entry per risk factor name into the scenario map
292+
for (const auto& e : byName) {
293+
series[s].emplace(e.second.first, e.second.second);
294+
}
295+
}
296+
297+
return series;
298+
}
299+
228300
const QuantLib::ext::shared_ptr<NPVCube>& HistoricalPnlGenerator::cube() const { return cube_; }
229301

230302
set<pair<string, Size>> HistoricalPnlGenerator::tradeIdIndexPairs() const {

OREAnalytics/orea/engine/historicalpnlgenerator.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class HistoricalPnlGenerator : public ore::data::ProgressReporter {
8585
the scenarios provided by the historical scenario generator. The historical
8686
scenarios will have the given \p filter applied.
8787
*/
88-
void generateCube(const QuantLib::ext::shared_ptr<ScenarioFilter>& filter);
88+
void generateCube(const QuantLib::ext::shared_ptr<ScenarioFilter>& filter, const bool runRiskFactorBreakdown = false);
8989

9090
/*! Return a vector of historical portfolio P&L values restricted to scenarios
9191
falling in \p period and restricted to the given \p tradeIds. The P&L values
@@ -136,6 +136,10 @@ class HistoricalPnlGenerator : public ore::data::ProgressReporter {
136136
time and the second dimension is tradeId.
137137
*/
138138
TradePnlStore tradeLevelPnl() const;
139+
140+
// Series of per-scenario portfolio PnL by risk factor (aligned with scenarios)
141+
using RiskFactorPnLSeries = std::vector<std::map<RiskFactorKey,QuantLib::Real>>;
142+
RiskFactorPnLSeries riskFactorLevelPnlSeries(const ore::data::TimePeriod& period) const;
139143

140144
/*! Return the last cube generated by generateCube.
141145
*/
@@ -155,6 +159,7 @@ class HistoricalPnlGenerator : public ore::data::ProgressReporter {
155159
QuantLib::ext::shared_ptr<HistoricalScenarioGenerator> hisScenGen_;
156160
QuantLib::ext::shared_ptr<NPVCube> cube_;
157161
QuantLib::ext::shared_ptr<ValuationEngine> valuationEngine_;
162+
std::map<RiskFactorKey, ext::shared_ptr<NPVCube>> mapCube_;
158163

159164
// additional parameters needed for multi-threaded ctor
160165
QuantLib::ext::shared_ptr<ore::data::EngineData> engineData_;

0 commit comments

Comments
 (0)