|
From: Lukasz S. <luk...@su...> - 2023-05-08 11:08:59
|
Hi Francois, Thank you for the quick reply. I guess my problem is simpler that you assumed. I'm just using the BondFunctions function 'yield' with a constant yield (a single number / rate; not a non-trivial forward curve). So, my question is just if the Newton procedure for finding yield of a bond gets correct inputs, because I ran into problems when trying to simply replicate the formulas I seem to find in the C++ QuantLib source code available in the web - the npv derivative seems to be modifiedDuration there, without multiplication of the result by npv, which seems wrong. But I may be mistaken. Best regards, Łukasz On 8.05.2023 11:47, Francois Botha wrote: > > *Warning: This message originated from outside the organization. Use > caution when following links or opening attachments.* > > Hi Lukasz, > > Just to note that QLNet is an independent C# port of Quantlib (the C++ > version). > > That said, I'm guessing you're bootstrapping either on Zero or > Forwards rates and the default implementations of these have rates set > to a maximum of 300% ( > https://github.com/amaggiulli/QLNet/blob/d3b709a76098d5765b11da83d0217786b118bd58/src/QLNet/Termstructures/Yield/Bootstraptraits.cs#L100 > and > https://github.com/amaggiulli/QLNet/blob/d3b709a76098d5765b11da83d0217786b118bd58/src/QLNet/Termstructures/Yield/Bootstraptraits.cs#L179). > > The implementation in Quantlib original is similar. I just briefly > glanced at the code and saw some branching related to the > QL_NEGATIVE_RATES preprocessor directive, which might be relevant. Try > enabling that directive. If that fails, you can implement your own > class that implements ITraits<YieldTermStructure> but removes the 300% > cap. I've had to use a variation of the last solution, but with my own > constraints, which in the end worked for me. > > Maybe someone else here has better suggestions. > > regards > Francois Botha > > > On Mon, 8 May 2023 at 11:10, Lukasz Skowronek > <luk...@su...> wrote: > > 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 > > > _______________________________________________ > QuantLib-users mailing list > Qua...@li... > https://lists.sourceforge.net/lists/listinfo/quantlib-users > |