|
From: Lukasz S. <luk...@su...> - 2023-05-08 09:06:50
|
Hi,
My name is Lukasz and I'm currently working on a small summary screen
for various bonds
that uses QuantLib under the hood.
I got an error report from our testers for the following inputs:
request.CouponPaymentsPerYear=1;
request.Coupon=0.0;
request.RedemptionPrice=100.0;
request.CleanPrice=1376.0;
request.FaceValue=1000.0;
...
request.ExpirationDate=newDateTime(2024, 6, 27);
request.InvestmentDate=newDateTime(2023, 2, 9);
In short, this input corresponds to a zero coupon bond priced extremely
high (at 1376%).
The error I'm getting says the solver cannot bracket the root of the
target function.
After shifting the dot in the clean price by one decimal place, I get
the code to work
as expected. Still, even if the testers did not intend to challenge my
code with
bonds priced at a 85% markup (that is, -85% yield), I would expect the
solver to
find the root for the original input.
Digging into QuantLib's source code, I traced a few pieces related to
how yield
calculation is really done:
In *bondfunctions.cpp (line 360):*
Rate BondFunctions::yield(const Bond& bond, ...
in *bondfunctions.hpp (line 160):*
static Rate yield(const Solver& solver, ...
in *cashflows.hpp (line 276):*
static Rate yield(const Solver& solver, ...
IrrFinder objFunction(leg, npv, dayCounter, compounding,
frequency, includeSettlementDateFlows,
settlementDate, npvDate);
return solver.solve(objFunction, accuracy, guess, guess/10.0);
and finally, in *cashflows.cpp (line 728):*
CashFlows::IrrFinder::IrrFinder(const Leg& leg, ...
Real CashFlows::IrrFinder::operator()(Rate y) const {
InterestRate yield(y, dayCounter_, compounding_, frequency_);
Real *NPV = CashFlows::npv(leg_, yield,**
**includeSettlementDateFlows_,**
** settlementDate_, npvDate_);*
return *npv_ - NPV*;
}
Real CashFlows::IrrFinder::derivative(Rate y) const {
InterestRate yield(y, dayCounter_, compounding_, frequency_);
return *modifiedDuration(leg_, yield,**
**includeSettlementDateFlows_,**
** settlementDate_, npvDate_)*;
}
Is this target function + derivative correct?
What worked on my side, when simulating the Newton method for finding
the root
separately, was:
doublef(doublerate) {
InterestRateir=newInterestRate(rate, dayCounter, Compounding.Compounded,
frequency);
return(cleanPrice+bond.accruedAmount(investmentDate))
*bond.notional(investmentDate) /100.0-
CashFlows.npv(bond.cashflows(), ir, false, investmentDate, investmentDate);
}
// <-- target function
doubled(doublerate) {
InterestRateir=newInterestRate(rate, dayCounter, Compounding.Compounded,
frequency);
returnCashFlows.modifiedDuration(bond.cashflows(), ir, false,
investmentDate, investmentDate) *CashFlows.npv(bond.cashflows(), ir,
false, investmentDate, investmentDate);
}
// <-- derivative
(this is C# code that has QLNet as dependency)
Hence, there is an additional multiplication by CashFlows.npv(..., which
plays well with the
definition of modified duration as -dPdy / P
There does not seem to be a similar factor in QuantLib.
Am I mistaken? Is the multiplication by NPV in the derivative handled
some other way?
Best regards,
Lukasz Skowronek
|