Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't require helpers when passing fixed parameters to fitted bond curve #2113

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 90 additions & 42 deletions ql/termstructures/yield/fittedbonddiscountcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,40 +85,82 @@ namespace QuantLib {
setup();
}

FittedBondDiscountCurve::FittedBondDiscountCurve(
Natural settlementDays,
const Calendar& calendar,
const FittingMethod& fittingMethod,
Array parameters,
Date maxDate,
const DayCounter& dayCounter)
: YieldTermStructure(settlementDays, calendar, dayCounter), accuracy_(1e-10),
maxEvaluations_(0), guessSolution_(std::move(parameters)),
maxDate_(maxDate), fittingMethod_(fittingMethod) {

fittingMethod_->curve_ = this;
setup();
}

FittedBondDiscountCurve::FittedBondDiscountCurve(
const Date& referenceDate,
const FittingMethod& fittingMethod,
Array parameters,
Date maxDate,
const DayCounter& dayCounter)
: YieldTermStructure(referenceDate, Calendar(), dayCounter), accuracy_(1e-10),
maxEvaluations_(0), guessSolution_(std::move(parameters)),
maxDate_(maxDate), fittingMethod_(fittingMethod) {

fittingMethod_->curve_ = this;
setup();
}


void FittedBondDiscountCurve::resetGuess(const Array& guess) {
QL_REQUIRE(guess.empty() || guess.size() == fittingMethod_->size(), "guess is of wrong size");
guessSolution_ = guess;
update();
}


void FittedBondDiscountCurve::performCalculations() const {

QL_REQUIRE(!bondHelpers_.empty(), "no bondHelpers given");

maxDate_ = Date::minDate();
Date refDate = referenceDate();

// double check bond quotes still valid and/or instruments not expired
for (Size i=0; i<bondHelpers_.size(); ++i) {
ext::shared_ptr<Bond> bond = bondHelpers_[i]->bond();
QL_REQUIRE(bondHelpers_[i]->quote()->isValid(),
io::ordinal(i+1) << " bond (maturity: " <<
bond->maturityDate() << ") has an invalid price quote");
Date bondSettlement = bond->settlementDate();
QL_REQUIRE(bondSettlement>=refDate,
io::ordinal(i+1) << " bond settlemente date (" <<
bondSettlement << ") before curve reference date (" <<
refDate << ")");
QL_REQUIRE(BondFunctions::isTradable(*bond, bondSettlement),
io::ordinal(i+1) << " bond non tradable at " <<
bondSettlement << " settlement date (maturity"
" being " << bond->maturityDate() << ")");
maxDate_ = std::max(maxDate_, bondHelpers_[i]->pillarDate());
bondHelpers_[i]->setTermStructure(
const_cast<FittedBondDiscountCurve*>(this));
if (maxEvaluations_!= 0) {
// we need to fit, so we require helpers
QL_REQUIRE(!bondHelpers_.empty(), "no bond helpers given");
}

if (maxEvaluations_ == 0) {
// no fit, but we need either an explicit max date or
// helpers from which to deduce it
QL_REQUIRE(maxDate_ != Date() || !bondHelpers_.empty(),
"no bond helpers or max date given");
}

if (!bondHelpers_.empty()) {
maxDate_ = Date::minDate();
Date refDate = referenceDate();

// double check bond quotes still valid and/or instruments not expired
for (Size i=0; i<bondHelpers_.size(); ++i) {
ext::shared_ptr<Bond> bond = bondHelpers_[i]->bond();
QL_REQUIRE(bondHelpers_[i]->quote()->isValid(),
io::ordinal(i+1) << " bond (maturity: " <<
bond->maturityDate() << ") has an invalid price quote");
Date bondSettlement = bond->settlementDate();
QL_REQUIRE(bondSettlement>=refDate,
io::ordinal(i+1) << " bond settlemente date (" <<
bondSettlement << ") before curve reference date (" <<
refDate << ")");
QL_REQUIRE(BondFunctions::isTradable(*bond, bondSettlement),
io::ordinal(i+1) << " bond non tradable at " <<
bondSettlement << " settlement date (maturity"
" being " << bond->maturityDate() << ")");
maxDate_ = std::max(maxDate_, bondHelpers_[i]->pillarDate());
bondHelpers_[i]->setTermStructure(
const_cast<FittedBondDiscountCurve*>(this));
}
}

fittingMethod_->init();
fittingMethod_->calculate();
}
Expand All @@ -141,6 +183,10 @@ namespace QuantLib {
}

void FittedBondDiscountCurve::FittingMethod::init() {

if (curve_->maxEvaluations_ == 0)
return; // we can skip the rest

// yield conventions
DayCounter yieldDC = curve_->dayCounter();
Compounding yieldComp = Compounded;
Expand Down Expand Up @@ -192,37 +238,39 @@ namespace QuantLib {

void FittedBondDiscountCurve::FittingMethod::calculate() {

FittingCost& costFunction = *costFunction_;

// start with the guess solution, if it exists
Array x(size(), 0.0);
if (!curve_->guessSolution_.empty()) {
QL_REQUIRE(curve_->guessSolution_.size() == size(), "wrong size for guess");
x = curve_->guessSolution_;
}

if (curve_->maxEvaluations_ == 0)
{
// Don't calculate, simply use the given parameters to provide a fitted curve.
// This turns the fittedbonddiscountcurve into an evaluator of the parametric
// curve, for example allowing to use the parameters for a credit spread curve
// calculated with bonds in one currency to be coupled to a discount curve in
// another currency.
// Don't calculate, simply use the given parameters to
// provide a fitted curve. This turns the instance into
// an evaluator of the parametric curve, for example
// allowing to use the parameters for a credit spread
// curve calculated with bonds in one currency to be
// coupled to a discount curve in another currency.

QL_REQUIRE(!curve_->guessSolution_.empty(), "no guess provided");
QL_REQUIRE(curve_->guessSolution_.size() == size(),
"wrong number of parameters");

solution_ = curve_->guessSolution_;

numberOfIterations_ = 0;
costValue_ = costFunction.value(solution_);
costValue_ = Null<Real>();
errorCode_ = EndCriteria::None;

return;
}

//workaround for backwards compatibility
FittingCost& costFunction = *costFunction_;

// start with the guess solution, if it exists
Array x(size(), 0.0);
if (!curve_->guessSolution_.empty()) {
QL_REQUIRE(curve_->guessSolution_.size() == size(), "wrong size for guess");
x = curve_->guessSolution_;
}

// workaround for backwards compatibility
ext::shared_ptr<OptimizationMethod> optimization = optimizationMethod_;
if(!optimization){
if (!optimization) {
optimization = ext::make_shared<Simplex>(curve_->simplexLambda_);
}
Problem problem(costFunction, constraint_, x);
Expand Down
16 changes: 16 additions & 0 deletions ql/termstructures/yield/fittedbonddiscountcurve.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ namespace QuantLib {
Array guess = Array(),
Real simplexLambda = 1.0,
Size maxStationaryStateIterations = 100);

//! curve reference date fixed for life of curve
FittedBondDiscountCurve(const Date& referenceDate,
std::vector<ext::shared_ptr<BondHelper> > bonds,
Expand All @@ -106,6 +107,21 @@ namespace QuantLib {
Array guess = Array(),
Real simplexLambda = 1.0,
Size maxStationaryStateIterations = 100);

//! don't fit, use precalculated parameters
FittedBondDiscountCurve(Natural settlementDays,
const Calendar& calendar,
const FittingMethod& fittingMethod,
Array parameters,
Date maxDate,
const DayCounter& dayCounter);

//! don't fit, use precalculated parameters
FittedBondDiscountCurve(const Date& referenceDate,
const FittingMethod& fittingMethod,
Array parameters,
Date maxDate,
const DayCounter& dayCounter);
//@}

//! \name Inspectors
Expand Down
50 changes: 28 additions & 22 deletions test-suite/fittedbonddiscountcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,37 @@ BOOST_AUTO_TEST_CASE(testEvaluation) {
BOOST_TEST_MESSAGE("Testing that fitted bond curves work as evaluators...");

Date today = Settings::instance().evaluationDate();
ext::shared_ptr<Bond> bond = ext::make_shared<ZeroCouponBond>(3, TARGET(), 100.0,
today + Period(10, Years));
Handle<Quote> q(ext::make_shared<SimpleQuote>(100.0));

std::vector<ext::shared_ptr<BondHelper> > helpers(1);
helpers[0] = ext::make_shared<BondHelper>(q, bond);
Date maxDate = today + Period(10, Years);

ExponentialSplinesFitting fittingMethod;

Size maxIterations = 0;
Array guess(9);
guess[0] = -51293.44;
guess[1] = -212240.36;
guess[2] = 168668.51;
guess[3] = 88792.74;
guess[4] = 120712.13;
guess[5] = -34332.83;
guess[6] = -66479.66;
guess[7] = 13605.17;
guess[8] = 0.0;
Array parameters = {
-51293.44,
-212240.36,
168668.51,
88792.74,
120712.13,
-34332.83,
-66479.66,
13605.17,
0.0
};

FittedBondDiscountCurve curve(0, TARGET(), helpers, Actual365Fixed(),
fittingMethod, 1e-10, maxIterations, guess);
FittedBondDiscountCurve curve1(
today, fittingMethod, parameters, maxDate, Actual365Fixed());

BOOST_CHECK_NO_THROW(curve.discount(3.0));
FittedBondDiscountCurve curve2(
0, TARGET(), fittingMethod, parameters, maxDate, Actual365Fixed());

// they work...
BOOST_CHECK_NO_THROW(curve1.discount(3.0));
BOOST_CHECK_NO_THROW(curve2.discount(3.0));

// ...but not after the max date
BOOST_CHECK_EXCEPTION(curve1.discount(12.0), Error,
ExpectedErrorMessage("past max curve time"));
BOOST_CHECK_EXCEPTION(curve2.discount(12.0), Error,
ExpectedErrorMessage("past max curve time"));
}

BOOST_AUTO_TEST_CASE(testFlatExtrapolation) {
Expand Down Expand Up @@ -315,9 +321,9 @@ BOOST_AUTO_TEST_CASE(testConstraint) {
Real accuracy = 1e-10; // default value
Size maxIterations = 10000; // default value
Array guess = {0.01}; // something positive so that initial value is in feasible region

FlatZero unconstrainedMethod;
FittedBondDiscountCurve unconstrainedCurve(0, TARGET(), helpers, Actual365Fixed(), unconstrainedMethod,
FittedBondDiscountCurve unconstrainedCurve(0, TARGET(), helpers, Actual365Fixed(), unconstrainedMethod,
accuracy, maxIterations, guess);
BOOST_CHECK_LT(unconstrainedCurve.fitResults().solution()[0], 0.0);

Expand Down
Loading