|
From: Francois B. <ig...@gm...> - 2023-05-08 09:47:57
|
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 = new DateTime(2024, 6, 27); > request.InvestmentDate = new DateTime(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: > double f(double rate) { > InterestRate ir = new InterestRate(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 > double d(double rate) { > InterestRate ir = new InterestRate(rate, dayCounter, Compounding. > Compounded, frequency); > return CashFlows.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 > |