Skip to content

Commit d033111

Browse files
mgronckijenkins
authored andcommitted
QPR-9859 allow overlapping close-out grid
1 parent ccd9d11 commit d033111

5 files changed

Lines changed: 133 additions & 71 deletions

File tree

OREAnalytics/orea/engine/valuationengine.cpp

Lines changed: 104 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -184,78 +184,62 @@ void ValuationEngine::buildCube(const boost::shared_ptr<data::Portfolio>& portfo
184184

185185
// loop over Dates, increase cubeDateIndex for each valuation date we hit
186186
int cubeDateIndex = -1;
187-
for (Size i = 0; i < dates.size(); ++i) {
188-
Date d = dates[i];
189-
190-
// Process auxiliary close-out dates first (may coincide with a valuation date, see below)
191-
// Store result at same cubeDateIndex as the previous valuation date's result, but at different cube depth
192-
// Differences to valuation date processing above:
193-
// Update valuation date and fixings, trades exercisable depending on stickiness
194-
bool scenarioUpdated = false;
195-
if (dg_->isCloseOutDate()[i]) {
196-
timer.start();
197-
198-
// update market
199-
simMarket_->preUpdate();
200-
if (!mporStickyDate)
201-
simMarket_->updateDate(d);
202-
simMarket_->updateScenario(d);
203-
scenarioUpdated = true;
204-
simMarket_->postUpdate(d, !mporStickyDate); // with fixings only if not sticky
205-
206-
recalibrateModels();
207-
208-
timer.stop();
209-
updateTime += timer.elapsed().wall * 1e-9;
210-
211-
// loop over trades
212-
timer.start();
213-
if (mporStickyDate) // switch off if sticky
214-
tradeExercisable(false, trades);
215-
QL_REQUIRE(cubeDateIndex >= 0,
216-
"negative cube date index, ensure that the date grid starts with a valuation date");
217-
runCalculators(true, trades, tradeHasError, calculators, outputCube, outputCubeNettingSet, d,
218-
cubeDateIndex, sample, simMarket_->label());
219-
if (mporStickyDate) // switch on again, if sticky
220-
tradeExercisable(true, trades);
221-
timer.stop();
222-
pricingTime += timer.elapsed().wall * 1e-9;
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_->closeOutDates()[i];
196+
std::tie(priceTime, upTime) = populateCube(
197+
valueDate, cubeDateIndex, sample, true, false, scenarioUpdated, trades, tradeHasError,
198+
calculators, outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
199+
pricingTime += priceTime;
200+
updateTime += upTime;
201+
std::tie(priceTime, upTime) =
202+
populateCube(closeOutDate, cubeDateIndex, sample, false, mporStickyDate, scenarioUpdated,
203+
trades, tradeHasError, calculators, outputCube, outputCubeNettingSet, counterparties,
204+
cptyCalculators, outputCptyCube);
205+
pricingTime += priceTime;
206+
updateTime += upTime;
223207
}
224-
225-
// process a valuation date as usual
226-
if (dg_->isValuationDate()[i]) {
227-
timer.start();
228-
229-
cubeDateIndex++;
230-
231-
// All the steps below from preUpdate() to updateAsd(d) are combined in update(d), but we decompose as
232-
// follows simMarket_->update(d);
233-
simMarket_->preUpdate();
234-
simMarket_->updateDate(d);
235-
// We can skip this step, if we have done that above in the close-out date section
236-
if (!scenarioUpdated)
237-
simMarket_->updateScenario(d);
238-
// Always with fixing update here, in contrast to the close-out date section
239-
simMarket_->postUpdate(d, true);
240-
// Aggregation scenario data update on valuation dates only
241-
simMarket_->updateAsd(d);
242-
243-
recalibrateModels();
244-
245-
timer.stop();
246-
updateTime += timer.elapsed().wall * 1e-9;
247-
248-
timer.start();
249-
// loop over trades
250-
runCalculators(false, trades, tradeHasError, calculators, outputCube, outputCubeNettingSet, d,
251-
cubeDateIndex, sample, simMarket_->label());
252-
// loop over counterparty names
253-
runCalculators(false, counterparties, cptyCalculators, outputCptyCube, d, cubeDateIndex, sample);
254-
timer.stop();
255-
pricingTime += timer.elapsed().wall * 1e-9;
208+
} else {
209+
std::map<Date, size_t> valueDateIndexCache;
210+
for (Size i = 0; i < dates.size(); ++i) {
211+
Date d = dates[i];
212+
// Process auxiliary close-out dates first (may coincide with a valuation date, see below)
213+
// Store result at same cubeDateIndex as the corresponding valuation date's result, but at different cube
214+
// depth Differences to valuation date processing above: Update valuation date and fixings, trades
215+
// exercisable depending on stickiness
216+
bool scenarioUpdated = false;
217+
if (dg_->isCloseOutDate()[i]) {
218+
double priceTime = 0;
219+
double upTime = 0;
220+
Date valueDate = dg_->valuationDateFromCloseOutDate(d);
221+
size_t valueDateIndex = valueDateIndexCache[valueDate];
222+
std::tie(priceTime, upTime) = populateCube(
223+
d, valueDateIndex, sample, false, false, scenarioUpdated, trades, tradeHasError,
224+
calculators, outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
225+
pricingTime += priceTime;
226+
updateTime += upTime;
227+
scenarioUpdated = true;
228+
}
229+
if (dg_->isValuationDate()[i]) {
230+
double priceTime = 0;
231+
double upTime = 0;
232+
++cubeDateIndex;
233+
valueDateIndexCache[d] = cubeDateIndex;
234+
std::tie(priceTime, upTime) = populateCube(
235+
d, cubeDateIndex, sample, true, false, scenarioUpdated, trades, tradeHasError,
236+
calculators, outputCube, outputCubeNettingSet, counterparties, cptyCalculators, outputCptyCube);
237+
pricingTime += priceTime;
238+
updateTime += upTime;
239+
scenarioUpdated = true;
240+
}
256241
}
257242
}
258-
259243
timer.start();
260244
simMarket_->fixingManager()->reset();
261245
fixingTime += timer.elapsed().wall * 1e-9;
@@ -352,5 +336,56 @@ void ValuationEngine::tradeExercisable(bool enable, const std::map<std::string,
352336
}
353337
}
354338
}
339+
340+
std::pair<double, double> ValuationEngine::populateCube(
341+
const QuantLib::Date& d, size_t cubeDateIndex, size_t sample, bool isValueDate, bool isStickyDate,
342+
bool scenarioUpdated, const std::map<std::string, boost::shared_ptr<Trade>>& trades,
343+
std::vector<bool>& tradeHasError, const std::vector<boost::shared_ptr<ValuationCalculator>>& calculators,
344+
boost::shared_ptr<analytics::NPVCube>& outputCube, boost::shared_ptr<analytics::NPVCube>& outputCubeNettingSet,
345+
const std::map<string, Size>& counterparties,
346+
const vector<boost::shared_ptr<CounterpartyCalculator>>& cptyCalculators,
347+
boost::shared_ptr<analytics::NPVCube>& outputCptyCube) {
348+
double pricingTime = 0;
349+
double updateTime = 0;
350+
QL_REQUIRE(cubeDateIndex >= 0, "first date should be a valuation date");
351+
cpu_timer timer;
352+
timer.start();
353+
// All the steps below from preUpdate() to updateAsd(d) are combined in update(d), but we decompose as
354+
// follows simMarket_->update(d);
355+
simMarket_->preUpdate();
356+
if (isValueDate || !isStickyDate) {
357+
simMarket_->updateDate(d);
358+
}
359+
// We can skip this step, if we have done that above in the close-out date section
360+
if (!scenarioUpdated)
361+
simMarket_->updateScenario(d);
362+
// Always with fixing update here, in contrast to the close-out date section
363+
simMarket_->postUpdate(d, true);
364+
// Aggregation scenario data update on valuation dates only
365+
if (isValueDate) {
366+
simMarket_->updateAsd(d);
367+
}
368+
recalibrateModels();
369+
370+
timer.stop();
371+
updateTime += timer.elapsed().wall * 1e-9;
372+
373+
timer.start();
374+
if (isStickyDate) // switch on again, if sticky
375+
tradeExercisable(false, trades);
376+
// loop over trades
377+
runCalculators(!isValueDate, trades, tradeHasError, calculators, outputCube, outputCubeNettingSet, d, cubeDateIndex,
378+
sample, simMarket_->label());
379+
if (isStickyDate) // switch on again, if sticky
380+
tradeExercisable(true, trades);
381+
// loop over counterparty names
382+
if (isValueDate) {
383+
runCalculators(false, counterparties, cptyCalculators, outputCptyCube, d, cubeDateIndex, sample);
384+
}
385+
timer.stop();
386+
pricingTime += timer.elapsed().wall * 1e-9;
387+
return std::make_pair(pricingTime, updateTime);
388+
}
389+
355390
} // namespace analytics
356391
} // namespace ore

OREAnalytics/orea/engine/valuationengine.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ class ValuationEngine : public ore::data::ProgressReporter {
101101

102102
private:
103103
void recalibrateModels();
104+
std::pair<double, double> populateCube(const QuantLib::Date& d, size_t cubeDateIndex, size_t sample,
105+
bool isValueDate, bool isStickyDate, bool scenarioUpdated,
106+
const std::map<std::string, boost::shared_ptr<Trade>>& trades,
107+
std::vector<bool>& tradeHasError,
108+
const std::vector<boost::shared_ptr<ValuationCalculator>>& calculators,
109+
boost::shared_ptr<analytics::NPVCube>& outputCube,
110+
boost::shared_ptr<analytics::NPVCube>& outputCubeNettingSet,
111+
const std::map<string, Size>& counterparties,
112+
const vector<boost::shared_ptr<CounterpartyCalculator>>& cptyCalculators,
113+
boost::shared_ptr<analytics::NPVCube>& outputCptyCube);
104114
void runCalculators(bool isCloseOutDate, const std::map<std::string, boost::shared_ptr<ore::data::Trade>>& trades,
105115
std::vector<bool>& tradeHasError,
106116
const std::vector<boost::shared_ptr<ValuationCalculator>>& calculators,

OREAnalytics/orea/scenario/scenariogenerator.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,14 @@ class ScenarioPathGenerator : public ScenarioGenerator {
7575
path_ = nextPath();
7676
pathStep_ = 0;
7777
}
78-
QL_REQUIRE(pathStep_ < dates_.size() && d == dates_[pathStep_], "step mismatch");
79-
return path_[pathStep_++]; // post increment
78+
QL_REQUIRE(pathStep_ < dates_.size(), "step mismatch");
79+
if(d == dates_[pathStep_]){
80+
return path_[pathStep_++]; // post increment
81+
} else{
82+
auto it = std::find(dates_.begin(), dates_.end(), d);
83+
QL_REQUIRE(it != dates_.end(), "invalid date " << d);
84+
return path_[std::distance(dates_.begin(), it)];
85+
}
8086
}
8187

8288
protected:

OREData/ored/utilities/dategrid.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ void DateGrid::addCloseOutDates(const QuantLib::Period& p) {
227227
c = calendar_.adjust(dates_[i] + p);
228228
else
229229
c = calendar_.advance(dates_[i], p, Following, false);
230+
closeOutDates_[c] = dates_[i];
230231
if (i < dates_.size() - 1) {
231232
// adjust the grid to ensure no overlap in valuation and closeout dates
232233
if (c >= dates_[i + 1]) {

OREData/ored/utilities/dategrid.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ class DateGrid {
101101
QuantLib::TimeGrid closeOutTimeGrid() const;
102102
//@}
103103

104+
//! Given a close out date it returns the corresponding value date
105+
QuantLib::Date valuationDateFromCloseOutDate(const QuantLib::Date& closeOutDate) const {
106+
auto it = closeOutDates_.find(closeOutDate);
107+
QL_REQUIRE(it != closeOutDates_.end(), "close out date " << closeOutDate << " not found in dategrid");
108+
return it->second;
109+
}
110+
//@}
111+
112+
104113
//! Accessor methods
105114
const QuantLib::Date& operator[](QuantLib::Size i) const { return dates_[i]; };
106115

@@ -112,6 +121,7 @@ class DateGrid {
112121
QuantLib::Calendar calendar_;
113122
QuantLib::DayCounter dayCounter_;
114123
std::vector<QuantLib::Date> dates_;
124+
std::map<QuantLib::Date, QuantLib::Date> closeOutDates_;
115125
std::vector<QuantLib::Period> tenors_;
116126
std::vector<QuantLib::Time> times_;
117127
QuantLib::TimeGrid timeGrid_;

0 commit comments

Comments
 (0)