Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
valuationengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <orea/cube/npvcube.hpp>
25
34
35#include <ql/errors.hpp>
36
37#include <boost/timer/timer.hpp>
38
39using namespace QuantLib;
40using namespace QuantExt;
41using namespace std;
42using namespace ore::data;
43using boost::timer::cpu_timer;
44using boost::timer::default_places;
45
46namespace ore {
47namespace analytics {
48
49ValuationEngine::ValuationEngine(const Date& today, const QuantLib::ext::shared_ptr<DateGrid>& dg,
50 const QuantLib::ext::shared_ptr<SimMarket>& simMarket,
51 const set<std::pair<string, QuantLib::ext::shared_ptr<ModelBuilder>>>& modelBuilders)
52 : today_(today), dg_(dg), simMarket_(simMarket), modelBuilders_(modelBuilders) {
53
54 QL_REQUIRE(dg_->size() > 0, "Error, DateGrid size must be > 0");
55 QL_REQUIRE(today <= dg_->dates().front(), "ValuationEngine: Error today ("
56 << today << ") must not be later than first DateGrid date "
57 << dg_->dates().front());
58 QL_REQUIRE(simMarket_, "ValuationEngine: Error, Null SimMarket");
59}
60
62 ObservationMode::Mode om = ObservationMode::instance().mode();
63 for (auto const& b : modelBuilders_) {
65 b.second->forceRecalculate();
66 b.second->recalibrate();
67 }
68}
69
70void ValuationEngine::buildCube(const QuantLib::ext::shared_ptr<data::Portfolio>& portfolio,
71 QuantLib::ext::shared_ptr<analytics::NPVCube> outputCube,
72 vector<QuantLib::ext::shared_ptr<ValuationCalculator>> calculators, bool mporStickyDate,
73 QuantLib::ext::shared_ptr<analytics::NPVCube> outputCubeNettingSet,
74 QuantLib::ext::shared_ptr<analytics::NPVCube> outputCptyCube,
75 vector<QuantLib::ext::shared_ptr<CounterpartyCalculator>> cptyCalculators, bool dryRun) {
76
77 struct SimMarketResetter {
78 SimMarketResetter(QuantLib::ext::shared_ptr<SimMarket> simMarket) : simMarket_(simMarket) {}
79 ~SimMarketResetter() { simMarket_->reset(); }
80 QuantLib::ext::shared_ptr<SimMarket> simMarket_;
81 } simMarketResetter(simMarket_);
82
83 LOG("Build cube with mporStickyDate=" << mporStickyDate << ", dryRun=" << std::boolalpha << dryRun);
84
85 QL_REQUIRE(portfolio->size() > 0, "ValuationEngine: Error portfolio is empty");
86
87 QL_REQUIRE(outputCube->numIds() == portfolio->trades().size(),
88 "cube x dimension (" << outputCube->numIds() << ") "
89 << "different from portfolio size (" << portfolio->trades().size() << ")");
90
91 QL_REQUIRE(outputCube->numDates() == dg_->valuationDates().size(),
92 "cube y dimension (" << outputCube->numDates() << ") "
93 << "different from number of valuation dates (" << dg_->valuationDates().size()
94 << ")");
95
96 if (outputCptyCube) {
97 QL_REQUIRE(outputCptyCube->numIds() == portfolio->counterparties().size() + 1,
98 "cptyCube x dimension (" << outputCptyCube->numIds() << "minus 1) "
99 << "different from portfolio counterparty size ("
100 << portfolio->counterparties().size() << ")");
101
102 QL_REQUIRE(outputCptyCube->numDates() == dg_->dates().size(), "outputCptyCube y dimension ("
103 << outputCptyCube->numDates() << ") "
104 << "different from number of time steps ("
105 << dg_->dates().size() << ")");
106 }
107
108 LOG("Starting ValuationEngine for " << portfolio->size() << " trades, " << outputCube->samples() << " samples and "
109 << dg_->size() << " dates.");
110
111 ObservationMode::Mode om = ObservationMode::instance().mode();
112 Real updateTime = 0.0;
113 Real pricingTime = 0.0;
114 Real fixingTime = 0.0;
115
116 LOG("Initialise " << calculators.size() << " valuation calculators");
117 for (auto const& c : calculators) {
118 c->init(portfolio, simMarket_);
119 c->initScenario();
120 }
121
122 // Loop is Samples, Dates, Trades
123 const auto& dates = dg_->dates();
124 const auto& trades = portfolio->trades();
125 auto& counterparties = outputCptyCube ? outputCptyCube->idsAndIndexes() : std::map<string, Size>();
126 std::vector<bool> tradeHasError(portfolio->size(), false);
127 LOG("Initialise state objects...");
128 // initialise state objects for each trade (required for path-dependent derivatives in particular)
129 size_t i = 0;
130 for (const auto& [tradeId, trade] : trades) {
131 QL_REQUIRE(!trade->npvCurrency().empty(), "NPV currency not set for trade " << trade->id());
132
133 DLOG("Initialise wrapper for trade " << trade->id());
134 trade->instrument()->initialise(dates);
135
137
138 // T0 values
139 try {
140 for (auto& calc : calculators)
141 calc->calculateT0(trade, i, simMarket_, outputCube, outputCubeNettingSet);
142 } catch (const std::exception& e) {
143 string expMsg = string("T0 valuation error: ") + e.what();
144 StructuredTradeErrorMessage(tradeId, trade->tradeType(), "ScenarioValuation", expMsg.c_str()).log();
145 tradeHasError[i] = true;
146 }
147
149 for (const Leg& leg : trade->legs()) {
150 for (Size n = 0; n < leg.size(); n++) {
151 QuantLib::ext::shared_ptr<FloatingRateCoupon> frc = QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(leg[n]);
152 if (frc) {
153 frc->unregisterWith(frc->index());
154 trade->instrument()->qlInstrument()->unregisterWith(frc);
155 // Unregister with eval dates
156 frc->unregisterWith(Settings::instance().evaluationDate());
157 frc->index()->unregisterWith(Settings::instance().evaluationDate());
158 trade->instrument()->qlInstrument()->unregisterWith(Settings::instance().evaluationDate());
159 }
160 }
161 }
162 }
163 ++i;
164 }
165 LOG("Total number of trades = " << portfolio->size());
166
167 if (!dates.empty() && dates.front() > simMarket_->asofDate()) {
168 // the fixing manager is only required if sim dates contain future dates
169 simMarket_->fixingManager()->initialise(portfolio, simMarket_);
170 }
171
172 cpu_timer timer;
173 cpu_timer loopTimer;
174 Size nTrades = trades.size();
175
176 // We call Cube::samples() each time here to allow for dynamic stopping times
177 // e.g. MC convergence tests
178 for (Size sample = 0; sample < (dryRun ? std::min<Size>(1, outputCube->samples()) : outputCube->samples());
179 ++sample) {
180 TLOG("ValuationEngine: apply scenario sample #" << sample);
181
182 for (auto& [tradeId, trade] : portfolio->trades())
183 trade->instrument()->reset();
184
185 // loop over Dates, increase cubeDateIndex for each valuation date we hit
186 int cubeDateIndex = -1;
187 if (!dg_->closeOutDates().empty() && mporStickyDate) {
188 // loop over dates and always do value date and close out date in one run
189 const bool scenarioUpdated = false;
190 for (size_t i = 0; i < dg_->valuationDates().size(); ++i) {
191 double priceTime = 0;
192 double upTime = 0;
193 ++cubeDateIndex;
194 Date valueDate = dg_->valuationDates()[i];
195 Date closeOutDate = dg_->closeOutDateFromValuationDate(valueDate);
196 std::tie(priceTime, upTime) = populateCube(
197 valueDate, cubeDateIndex, sample, true, false, scenarioUpdated, trades, tradeHasError, calculators,
198 outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
199 pricingTime += priceTime;
200 updateTime += upTime;
201 if(closeOutDate != Date()){
202 std::tie(priceTime, upTime) = populateCube(
203 closeOutDate, cubeDateIndex, sample, false, mporStickyDate, scenarioUpdated, trades, tradeHasError,
204 calculators, outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
205 pricingTime += priceTime;
206 updateTime += upTime;
207 }
208 }
209 } else {
210 std::map<Date, std::vector<size_t>> closeOutDateToValueDateIndex;
211 for (Size i = 0; i < dates.size(); ++i) {
212 Date d = dates[i];
213 // Process auxiliary close-out dates first (may coincide with a valuation date, see below)
214 // Store result at same cubeDateIndex as the corresponding valuation date's result, but at different
215 // cube depth Differences to valuation date processing above: Update valuation date and fixings, trades
216 // exercisable depending on stickiness
217 bool scenarioUpdated = false;
218 if (dg_->isCloseOutDate()[i]) {
219 double priceTime = 0;
220 double upTime = 0;
221 QL_REQUIRE(closeOutDateToValueDateIndex.count(d) == 1 && !closeOutDateToValueDateIndex[d].empty(),
222 "Need to calculate valuation date before close out date");
223 for (size_t& valueDateIndex : closeOutDateToValueDateIndex[d]) {
224 std::tie(priceTime, upTime) =
225 populateCube(d, valueDateIndex, sample, false, mporStickyDate, scenarioUpdated, trades,
226 tradeHasError, calculators, outputCube, outputCubeNettingSet, counterparties,
227 cptyCalculators, outputCptyCube);
228 pricingTime += priceTime;
229 updateTime += upTime;
230 scenarioUpdated = true;
231 }
232 }
233 if (dg_->isValuationDate()[i]) {
234 double priceTime = 0;
235 double upTime = 0;
236 ++cubeDateIndex;
237 Date closeOutDate = dg_->closeOutDateFromValuationDate(d);
238 if(closeOutDate != Date())
239 closeOutDateToValueDateIndex[closeOutDate].push_back(cubeDateIndex);
240 std::tie(priceTime, upTime) = populateCube(
241 d, cubeDateIndex, sample, true, false, scenarioUpdated, trades, tradeHasError, calculators,
242 outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
243 pricingTime += priceTime;
244 updateTime += upTime;
245 scenarioUpdated = true;
246 }
247 }
248 }
249
250 std::ostringstream detail;
251 detail << nTrades << " trade" << (nTrades == 1 ? "" : "s") << ", " << outputCube->samples() << " sample"
252 << (outputCube->samples() == 1 ? "" : "s");
253 updateProgress(sample * nTrades, outputCube->samples() * nTrades, detail.str());
254
255 timer.start();
256 simMarket_->fixingManager()->reset();
257 fixingTime += timer.elapsed().wall * 1e-9;
258 }
259
260 if (dryRun) {
261 LOG("Doing a dry run - fill remaining cube with random values.");
262 for (Size sample = 1; sample < outputCube->samples(); ++sample) {
263 for (Size i = 0; i < dates.size(); ++i) {
264 for (Size j = 0; j < trades.size(); ++j) {
265 for (Size d = 0; d < outputCube->depth(); ++d) {
266 // add some noise, but only for the first few samples, so that e.g.
267 // a sensi run is not polluted with too many sensis for each trade
268 Real noise = sample < 10 ? static_cast<Real>(i + j + d + sample) : 0.0;
269 outputCube->set(outputCube->getT0(j, d) + noise, j, i, sample, d);
270 }
271 }
272 }
273 }
274 }
275
276 std::ostringstream detail;
277 detail << nTrades << " trade" << (nTrades == 1 ? "" : "s") << ", " << outputCube->samples() << " sample"
278 << (outputCube->samples() == 1 ? "" : "s");
279 updateProgress(outputCube->samples() * nTrades, outputCube->samples() * nTrades, detail.str());
280 loopTimer.stop();
281 LOG("ValuationEngine completed: loop " << setprecision(2) << loopTimer.format(2, "%w") << " sec, "
282 << "pricing " << pricingTime << " sec, "
283 << "update " << updateTime << " sec "
284 << "fixing " << fixingTime);
285
286 // for trades with errors set all output cube values to zero
287 i = 0;
288 for (auto& [tradeId, trade] : trades) {
289 if (tradeHasError[i]) {
290 ALOG("setting all results in output cube to zero for trade '"
291 << tradeId << "' since there was at least one error during simulation");
292 outputCube->remove(i);
293 }
294 i++;
295 }
296}
297
298void ValuationEngine::runCalculators(bool isCloseOutDate, const std::map<std::string, QuantLib::ext::shared_ptr<Trade>>& trades,
299 std::vector<bool>& tradeHasError,
300 const std::vector<QuantLib::ext::shared_ptr<ValuationCalculator>>& calculators,
301 QuantLib::ext::shared_ptr<analytics::NPVCube>& outputCube,
302 QuantLib::ext::shared_ptr<analytics::NPVCube>& outputCubeNettingSet, const Date& d,
303 const Size cubeDateIndex, const Size sample, const string& label) {
304 ObservationMode::Mode om = ObservationMode::instance().mode();
305 for (auto& calc : calculators)
306 calc->initScenario();
307 // loop over trades
308 size_t j = 0;
309 for (auto tradeIt = trades.begin(); tradeIt != trades.end(); ++tradeIt, ++j) {
310 auto trade = tradeIt->second;
311 if (tradeHasError[j]) {
312 continue;
313 }
314
315 // We can avoid checking mode here and always call updateQlInstruments()
317 trade->instrument()->updateQlInstruments();
318 try {
319 for (auto& calc : calculators)
320 calc->calculate(trade, j, simMarket_, outputCube, outputCubeNettingSet, d, cubeDateIndex, sample,
321 isCloseOutDate);
322 } catch (const std::exception& e) {
323 string expMsg = "date = " + ore::data::to_string(io::iso_date(d)) +
324 ", sample = " + ore::data::to_string(sample) + ", label = " + label + ": " + e.what();
325 StructuredTradeErrorMessage(trade->id(), trade->tradeType(), "ScenarioValuation", expMsg.c_str()).log();
326 tradeHasError[j] = true;
327 }
328 }
329}
330
331void ValuationEngine::runCalculators(bool isCloseOutDate, const std::map<std::string, Size>& counterparties,
332 const std::vector<QuantLib::ext::shared_ptr<CounterpartyCalculator>>& calculators,
333 QuantLib::ext::shared_ptr<analytics::NPVCube>& cptyCube, const Date& d,
334 const Size cubeDateIndex, const Size sample) {
335 // loop over counterparties
336 for (const auto& [counterparty, idx] : counterparties) {
337 for (auto& calc : calculators) {
338 calc->calculate(counterparty, idx, simMarket_, cptyCube, d, cubeDateIndex, sample, isCloseOutDate);
339 }
340 }
341}
342
343void ValuationEngine::tradeExercisable(bool enable, const std::map<std::string, QuantLib::ext::shared_ptr<Trade>>& trades) {
344 for (const auto& [tradeId, trade] : trades) {
345 auto t = QuantLib::ext::dynamic_pointer_cast<OptionWrapper>(trade->instrument());
346 if (t != nullptr) {
347 if (enable)
348 t->enableExercise();
349 else
350 t->disableExercise();
351 }
352 }
353}
354
355std::pair<double, double> ValuationEngine::populateCube(
356 const QuantLib::Date& d, size_t cubeDateIndex, size_t sample, bool isValueDate, bool isStickyDate,
357 bool scenarioUpdated, const std::map<std::string, QuantLib::ext::shared_ptr<Trade>>& trades,
358 std::vector<bool>& tradeHasError, const std::vector<QuantLib::ext::shared_ptr<ValuationCalculator>>& calculators,
359 QuantLib::ext::shared_ptr<analytics::NPVCube>& outputCube, QuantLib::ext::shared_ptr<analytics::NPVCube>& outputCubeNettingSet,
360 const std::map<string, Size>& counterparties,
361 const vector<QuantLib::ext::shared_ptr<CounterpartyCalculator>>& cptyCalculators,
362 QuantLib::ext::shared_ptr<analytics::NPVCube>& outputCptyCube) {
363 double pricingTime = 0;
364 double updateTime = 0;
365 QL_REQUIRE(cubeDateIndex >= 0, "first date should be a valuation date");
366 cpu_timer timer;
367 timer.start();
368 simMarket_->preUpdate();
369 if (isValueDate || !isStickyDate) {
370 simMarket_->updateDate(d);
371 }
372 // We can skip this step, if we have done that above in the close-out date section
373 if (!scenarioUpdated) {
374 simMarket_->updateScenario(d);
375 }
376 // Always with fixing update here, in contrast to the close-out date section
377 simMarket_->postUpdate(d, !isStickyDate || isValueDate);
378 // Aggregation scenario data update on valuation dates only
379 if (isValueDate) {
380 simMarket_->updateAsd(d);
381 }
383
384 timer.stop();
385 updateTime += timer.elapsed().wall * 1e-9;
386
387 timer.start();
388 if (isStickyDate && !isValueDate) // switch on again, if sticky
389 tradeExercisable(false, trades);
390 // loop over trades
391 runCalculators(!isValueDate, trades, tradeHasError, calculators, outputCube, outputCubeNettingSet, d, cubeDateIndex,
392 sample, simMarket_->label());
393 if (isStickyDate && !isValueDate) // switch on again, if sticky
394 tradeExercisable(true, trades);
395 // loop over counterparty names
396 if (isValueDate) {
397 runCalculators(false, counterparties, cptyCalculators, outputCptyCube, d, cubeDateIndex, sample);
398 }
399 timer.stop();
400 pricingTime += timer.elapsed().wall * 1e-9;
401 return std::make_pair(pricingTime, updateTime);
402}
403
404} // namespace analytics
405} // namespace ore
QuantLib::ext::shared_ptr< ore::data::DateGrid > dg_
QuantLib::ext::shared_ptr< ore::analytics::SimMarket > simMarket_
std::pair< double, double > populateCube(const QuantLib::Date &d, size_t cubeDateIndex, size_t sample, bool isValueDate, bool isStickyDate, bool scenarioUpdated, const std::map< std::string, QuantLib::ext::shared_ptr< ore::data::Trade > > &trades, std::vector< bool > &tradeHasError, const std::vector< QuantLib::ext::shared_ptr< ValuationCalculator > > &calculators, QuantLib::ext::shared_ptr< analytics::NPVCube > &outputCube, QuantLib::ext::shared_ptr< analytics::NPVCube > &outputCubeNettingSet, const std::map< std::string, size_t > &counterparties, const std::vector< QuantLib::ext::shared_ptr< CounterpartyCalculator > > &cptyCalculators, QuantLib::ext::shared_ptr< analytics::NPVCube > &outputCptyCube)
set< std::pair< std::string, QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > > > modelBuilders_
void buildCube(const QuantLib::ext::shared_ptr< data::Portfolio > &portfolio, QuantLib::ext::shared_ptr< analytics::NPVCube > outputCube, std::vector< QuantLib::ext::shared_ptr< ValuationCalculator > > calculators, bool mporStickyDate=true, QuantLib::ext::shared_ptr< analytics::NPVCube > outputCubeNettingSet=nullptr, QuantLib::ext::shared_ptr< analytics::NPVCube > outputCptyCube=nullptr, std::vector< QuantLib::ext::shared_ptr< CounterpartyCalculator > > cptyCalculators={}, bool dryRun=false)
Build NPV cube.
ValuationEngine(const QuantLib::Date &today, const QuantLib::ext::shared_ptr< ore::data::DateGrid > &dg, const QuantLib::ext::shared_ptr< analytics::SimMarket > &simMarket, const set< std::pair< std::string, QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > > > &modelBuilders=set< std::pair< std::string, QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > > >())
Constructor.
void runCalculators(bool isCloseOutDate, const std::map< std::string, QuantLib::ext::shared_ptr< ore::data::Trade > > &trades, std::vector< bool > &tradeHasError, const std::vector< QuantLib::ext::shared_ptr< ValuationCalculator > > &calculators, QuantLib::ext::shared_ptr< analytics::NPVCube > &outputCube, QuantLib::ext::shared_ptr< analytics::NPVCube > &outputCubeSensis, const QuantLib::Date &d, const QuantLib::Size cubeDateIndex, const QuantLib::Size sample, const std::string &label="")
void tradeExercisable(bool enable, const std::map< std::string, QuantLib::ext::shared_ptr< ore::data::Trade > > &trades)
void updateProgress(const unsigned long progress, const unsigned long total, const std::string &detail="")
The counterparty cube calculator interface.
#define LOG(text)
#define DLOG(text)
#define ALOG(text)
#define TLOG(text)
Size size(const ValueType &v)
std::string to_string(const LocationInfo &l)
The base NPV cube class.
Singleton class to hold global Observation Mode.
A Market class that can be Simulated.
The counterparty cube calculator interface.
The cube valuation core.