From 0eff87734e487f4bb672a97bf634c7e4d0004caa Mon Sep 17 00:00:00 2001 From: Peter Caspers Date: Sat, 1 Nov 2025 14:02:31 +0100 Subject: [PATCH 1/6] QPR-13654 move built flag to trade level --- OREData/ored/portfolio/portfolio.cpp | 10 +++++++--- OREData/ored/portfolio/portfolio.hpp | 3 +-- OREData/ored/portfolio/trade.cpp | 2 ++ OREData/ored/portfolio/trade.hpp | 7 +++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/OREData/ored/portfolio/portfolio.cpp b/OREData/ored/portfolio/portfolio.cpp index 22317fbd2f..44529070c5 100644 --- a/OREData/ored/portfolio/portfolio.cpp +++ b/OREData/ored/portfolio/portfolio.cpp @@ -42,7 +42,6 @@ void Portfolio::clear() { } void Portfolio::reset() { - isBuilt_ = false; LOG("Reset portfolio of size " << trades_.size()); for (auto [id, t] : trades_) t->reset(); @@ -175,9 +174,13 @@ void Portfolio::build(const QuantLib::ext::shared_ptr& engineFact .log(); } QL_REQUIRE(trades_.size() > 0, "Portfolio does not contain any built trades, context is '" + context + "'"); - isBuilt_ = true; } +bool Portfolio::isBuilt() const { + return std::all_of(trades_.begin(), trades_.end(), [](const auto& s) { return s.second->isBuilt(); }); +} + + Date Portfolio::maturity() const { QL_REQUIRE(trades_.size() > 0, "Cannot get maturity of an empty portfolio"); Date mat = Date::minDate(); @@ -221,7 +224,6 @@ void Portfolio::add(const QuantLib::ext::shared_ptr& trade) { QL_REQUIRE(!has(trade->id()), "Attempted to add a trade to the portfolio with an id, which already exists."); underlyingIndicesCache_.clear(); trades_[trade->id()] = trade; - isBuilt_ = false; } bool Portfolio::has(const string& id) { return trades_.find(id) != trades_.end(); } @@ -308,6 +310,7 @@ std::pair, bool> buildTrade(QuantLib::ext::shar try { trade->reset(); trade->build(engineFactory); + trade->setBuilt(); TLOG("Required Fixings for trade " << trade->id() << ":"); TLOGGERSTREAM(trade->requiredFixings()); return std::make_pair(nullptr, true); @@ -326,6 +329,7 @@ std::pair, bool> buildTrade(QuantLib::ext::shar failed->setEnvelope(trade->envelope()); failed->build(engineFactory); failed->resetPricingStats(trade->getNumberOfPricings(), trade->getCumulativePricingTime()); + failed->setBuilt(); LOG("Built failed trade with id " << failed->id()); return std::make_pair(failed, false); } else { diff --git a/OREData/ored/portfolio/portfolio.hpp b/OREData/ored/portfolio/portfolio.hpp index e966b13a4d..8630634f5e 100644 --- a/OREData/ored/portfolio/portfolio.hpp +++ b/OREData/ored/portfolio/portfolio.hpp @@ -87,7 +87,7 @@ class Portfolio : public XMLSerializable { const bool emitStructuredError = true); //! if the portfolio has been built - bool isBuilt() const { return isBuilt_; } + bool isBuilt() const; //! Calculates the maturity of the portfolio QuantLib::Date maturity() const; @@ -138,7 +138,6 @@ class Portfolio : public XMLSerializable { bool buildFailedTrades_, ignoreTradeBuildFail_; std::map> trades_; std::map> underlyingIndicesCache_; - bool isBuilt_ = false; }; std::pair, bool> buildTrade( diff --git a/OREData/ored/portfolio/trade.cpp b/OREData/ored/portfolio/trade.cpp index bbb5bf74bf..12c98fb86d 100644 --- a/OREData/ored/portfolio/trade.cpp +++ b/OREData/ored/portfolio/trade.cpp @@ -300,6 +300,8 @@ void Trade::reset() { savedNumberOfPricings_ += instrument_->getNumberOfPricings(); savedCumulativePricingTime_ += instrument_->getCumulativePricingTime(); } + // reset build status + setBuilt(false); // reset members instrument_ = QuantLib::ext::shared_ptr(); legs_.clear(); diff --git a/OREData/ored/portfolio/trade.hpp b/OREData/ored/portfolio/trade.hpp index 429ed3bdf6..06ccce7de4 100644 --- a/OREData/ored/portfolio/trade.hpp +++ b/OREData/ored/portfolio/trade.hpp @@ -246,6 +246,12 @@ class Trade : public XMLSerializable { const std::string& configuration, const bool includePastCashflows) const; + /* set build status, this flag is maintained in buildTrade() and Trade::reset(), i.e. _not_ in Trade::build() */ + void setBuilt(const bool b = true) const { isBuilt_ = b; } + + /* get build status */ + bool isBuilt() const { return isBuilt_; } + protected: string tradeType_; // class name of the derived class QuantLib::ext::shared_ptr instrument_; @@ -294,6 +300,7 @@ class Trade : public XMLSerializable { string id_; Envelope envelope_; TradeActions tradeActions_; + mutable bool isBuilt_ = false; }; template From 1c7fb035ee78e89f7f192a6141f11deffd414e25 Mon Sep 17 00:00:00 2001 From: Peter Caspers Date: Tue, 4 Nov 2025 14:59:22 +0100 Subject: [PATCH 2/6] align submodule --- ORE-SWIG/QuantLib-SWIG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ORE-SWIG/QuantLib-SWIG b/ORE-SWIG/QuantLib-SWIG index 9b1d648856..18203bf0af 160000 --- a/ORE-SWIG/QuantLib-SWIG +++ b/ORE-SWIG/QuantLib-SWIG @@ -1 +1 @@ -Subproject commit 9b1d6488567f1cc84ab55d4c08cfef7ea7d1550b +Subproject commit 18203bf0af4639a51a08c705e7b07bc864d2116b From 585a2c3a8b672d16fbc4993cdfbbbffc485a8e4f Mon Sep 17 00:00:00 2001 From: farahkhashman Date: Wed, 21 Jan 2026 11:33:25 +0000 Subject: [PATCH 3/6] merge conflict fix v1.8.14.1 --- QuantLib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuantLib b/QuantLib index ee9cf1dde5..a6ef3224df 160000 --- a/QuantLib +++ b/QuantLib @@ -1 +1 @@ -Subproject commit ee9cf1dde56965141be6fa87ddfce9fa42034580 +Subproject commit a6ef3224dfe0024debde0a14ca6e0af6d8a00ee1 From 9b3150d9b7c57efc26fda1b834afcad4f1832172 Mon Sep 17 00:00:00 2001 From: farahkhashman Date: Wed, 21 Jan 2026 13:30:57 +0000 Subject: [PATCH 4/6] align submodules --- ORE-SWIG/QuantLib-SWIG | 2 +- QuantLib | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ORE-SWIG/QuantLib-SWIG b/ORE-SWIG/QuantLib-SWIG index 18203bf0af..b1184e42d4 160000 --- a/ORE-SWIG/QuantLib-SWIG +++ b/ORE-SWIG/QuantLib-SWIG @@ -1 +1 @@ -Subproject commit 18203bf0af4639a51a08c705e7b07bc864d2116b +Subproject commit b1184e42d4a544bd46f260b825d4fe7e31442450 diff --git a/QuantLib b/QuantLib index a6ef3224df..2937e4bc8f 160000 --- a/QuantLib +++ b/QuantLib @@ -1 +1 @@ -Subproject commit a6ef3224dfe0024debde0a14ca6e0af6d8a00ee1 +Subproject commit 2937e4bc8f3e44c16ea1149cfcbe12a8eed1ea29 From c8c0b65b7bbe15e202c105764c6a57b126f61934 Mon Sep 17 00:00:00 2001 From: Zetterberg <119352036+oszette@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:09:53 +0100 Subject: [PATCH 5/6] Add fixingDays parameter to applyStubInterpolation --- OREData/ored/portfolio/legdata.cpp | 2 +- OREData/ored/portfolio/legdata.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/OREData/ored/portfolio/legdata.cpp b/OREData/ored/portfolio/legdata.cpp index 2ead26e77b..ba1fd98956 100644 --- a/OREData/ored/portfolio/legdata.cpp +++ b/OREData/ored/portfolio/legdata.cpp @@ -1231,7 +1231,7 @@ Leg makeZCFixedLeg(const LegData& data, const QuantLib::Date& openEndDateReplace void applyStubInterpolation(Leg::iterator c, const std::string& shortIndexStr, const std::string& longIndexStr, const std::string& roundingTypeStr, const std::string& roundingPrecisionStr, const QuantLib::ext::shared_ptr& engineFactory, - const bool useOriginalIndexCurve, const Size accrualDays) { + const bool useOriginalIndexCurve, const Size accrualDays, const Size fixingDays) { if (shortIndexStr.empty() && longIndexStr.empty()) { return; } diff --git a/OREData/ored/portfolio/legdata.hpp b/OREData/ored/portfolio/legdata.hpp index 1f89a483ea..a3dfcf02dc 100644 --- a/OREData/ored/portfolio/legdata.hpp +++ b/OREData/ored/portfolio/legdata.hpp @@ -1259,7 +1259,8 @@ Leg buildNotionalLeg(const LegData& data, const Leg& leg, RequiredFixings& requi void applyStubInterpolation(Leg::iterator c, const std::string& shortIndexStr, const std::string& longIndexStr, const std::string& roundingTypeStr, const std::string& roundingPrecisionStr, const QuantLib::ext::shared_ptr& engineFactory, - const bool useOriginalIndexCurve, const Size accrualDays = Null()); + const bool useOriginalIndexCurve, const Size accrualDays = Null(), + const Size fixingDays = Null()); } // namespace data } // namespace ore From bd6f40ae3d9ad6c68ccb230d5beba8ac4e3611d1 Mon Sep 17 00:00:00 2001 From: Zetterberg <119352036+oszette@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:16:29 +0100 Subject: [PATCH 6/6] Pass fixingDays from floatData or index IborCoupon can be created without defining fixingDays, which will cause errors. To mitigate this, one solution is to be able to pass in fixingDays into applyStubInterpolation and use it if it is defined, if not you can try and retrieve it from the coupon (which still ofc can fail). --- OREData/ored/portfolio/legdata.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/OREData/ored/portfolio/legdata.cpp b/OREData/ored/portfolio/legdata.cpp index ba1fd98956..7b4a7a44b2 100644 --- a/OREData/ored/portfolio/legdata.cpp +++ b/OREData/ored/portfolio/legdata.cpp @@ -1253,6 +1253,7 @@ void applyStubInterpolation(Leg::iterator c, const std::string& shortIndexStr, c iborCpn = QuantLib::ext::dynamic_pointer_cast(*c); } QL_REQUIRE(iborCpn, "applyStubInterpolation(): unable to unpack coupon to ibor coupon"); + Size fDays = fixingDays == Null() ? iborCpn->fixingDays() : fixingDays; // ... replace it with an interpolated Ibor Coupon ... QuantLib::ext::shared_ptr idx1, idx2; if (!shortIndexStr.empty()) @@ -1269,7 +1270,7 @@ void applyStubInterpolation(Leg::iterator c, const std::string& shortIndexStr, c // actually no interpolation, only one index is given effectively, so we can use an Ibor Coupon tmp = QuantLib::ext::make_shared( iborCpn->date(), iborCpn->nominal(), iborCpn->accrualStartDate(), iborCpn->accrualEndDate(), - iborCpn->fixingDays(), + fDays, useOriginalIndexCurve ? idx1->clone(iborCpn->iborIndex()->forwardingTermStructure()) : idx1, iborCpn->gearing(), iborCpn->spread(), iborCpn->referencePeriodStart(), iborCpn->referencePeriodEnd(), iborCpn->dayCounter(), iborCpn->isInArrears(), iborCpn->exCouponDate()); @@ -1287,7 +1288,7 @@ void applyStubInterpolation(Leg::iterator c, const std::string& shortIndexStr, c useOriginalIndexCurve ? iborCpn->iborIndex()->forwardingTermStructure() : Handle()); tmp = QuantLib::ext::make_shared( iborCpn->date(), iborCpn->nominal(), iborCpn->accrualStartDate(), iborCpn->accrualEndDate(), - iborCpn->fixingDays(), interpolatedIndex, iborCpn->gearing(), iborCpn->spread(), + fDays, interpolatedIndex, iborCpn->gearing(), iborCpn->spread(), iborCpn->referencePeriodStart(), iborCpn->referencePeriodEnd(), iborCpn->dayCounter(), iborCpn->isInArrears(), iborCpn->exCouponDate(), iborCpn->iborIndex()); DLOG("created InterpolatedIborIndex for accrual period " @@ -1465,7 +1466,7 @@ Leg makeIborLeg(const LegData& data, const QuantLib::ext::shared_ptr& // front / back stub interpolation applyStubInterpolation(leg.begin(), floatData->frontStubShortIndex(), floatData->frontStubLongIndex(), floatData->frontStubRoundingType(), floatData->frontStubRoundingPrecision(), engineFactory, - floatData->stubUseOriginalCurve()); + floatData->stubUseOriginalCurve(), Null(), fixingDays); if (leg.size() == 1 && !floatData->frontStubShortIndex().empty() && !floatData->frontStubLongIndex().empty() && !floatData->backStubShortIndex().empty() && !floatData->backStubLongIndex().empty()) { @@ -1474,7 +1475,7 @@ Leg makeIborLeg(const LegData& data, const QuantLib::ext::shared_ptr& } else { applyStubInterpolation(leg.end() - 1, floatData->backStubShortIndex(), floatData->backStubLongIndex(), floatData->backStubRoundingType(), floatData->backStubRoundingPrecision(), engineFactory, - floatData->stubUseOriginalCurve()); + floatData->stubUseOriginalCurve(), Null(), fixingDays); } return leg; } @@ -1588,7 +1589,7 @@ Leg makeIborLeg(const LegData& data, const QuantLib::ext::shared_ptr& // front / back stub interpolation applyStubInterpolation(tmpLeg.begin(), floatData->frontStubShortIndex(), floatData->frontStubLongIndex(), floatData->frontStubRoundingType(), floatData->frontStubRoundingPrecision(), engineFactory, - floatData->stubUseOriginalCurve()); + floatData->stubUseOriginalCurve(), Null(), fixingDays); if (tmpLeg.size() == 1 && !floatData->frontStubShortIndex().empty() && !floatData->frontStubLongIndex().empty() && !floatData->backStubShortIndex().empty() && !floatData->backStubLongIndex().empty()) { @@ -1597,7 +1598,7 @@ Leg makeIborLeg(const LegData& data, const QuantLib::ext::shared_ptr& } else { applyStubInterpolation(tmpLeg.end() - 1, floatData->backStubShortIndex(), floatData->backStubLongIndex(), floatData->backStubRoundingType(), floatData->backStubRoundingPrecision(), engineFactory, - floatData->stubUseOriginalCurve()); + floatData->stubUseOriginalCurve(), Null(), fixingDays); } // return the leg