Hi
I’m very new to Quantlib - about a week in (quite amazed so far!), but I have managed to get some bonds working in Python 2.7, using SWIG. QL v 1.9.
I have set up a bond and the schedule is correct. When I calculate the clean price by inputting a yield (using BondFunctions.cleanPrice(…pass in yield)), this is correct (and yield from clean price), accrued is also correct and duration is fine
as yield is not far out.
However, when I attach a yield curve (flat forward) with the same yield, I get a fractionally different / price yield calc. Dates in the schedule are unadjusted, not modified following.
Results:
Clean Price Expected : 179.03
Clean Price BondFunctions.cleanPrice() : 179.03
Clean Price bond.cleanPrice() : 179.09
Difference : 0.0626431535518
Yield Expected : -0.0145461
Yield BondFunctions.BondYield() : -0.0145461
Yield bond.bondYield() : -0.01455624
Difference : 1.01382016107e-05
Duration Expected : 34.52
Duration BondFunctions.duration() : 34.52
Accrued Expected : 0.0483425414365
Accrued BondFunctions.accruedAmount() : 0.0483425414365
Accrued bond.accruedAmount() : 0.0483425414365
Program ended with exit code: 0
As you can see the calculated clean price from bond.cleanPrice() is 6 cents different (incorrect)..and it’s been driving me mad!! The
yield is different from the 5th decimal place.
This is a real bond and the input numbers have been verified using a manual calc in excel, they match the officially published numbers
from the UK debt management office (negative yield
is the real yield for an inflation linked gilt, i.e. the quote of 179.03 is the market quote (clean) for the UKTI 52’s, before the index
ratio is applied).
In this example the price is the closing price for 30th Nov and settlement date is 1st Dec, 2016.
If anyone can point out where I have gone wrong it would be much appreciated.
Code is below.
Many thanks
Nick
import QuantLib
as ql
import datetime
as dt
calendar = ql.UnitedKingdom()
coupons=[0.0025]
exCouponPeriod=ql.Period(6,ql.Days)
calendarCoupon=ql.UnitedKingdom()
faceValue=100
businessConvention = ql.Unadjusted
businessConventionCoupon=ql.Unadjusted
today = ql.Date(30,11,2016)
evaluationDate = calendar.adjust(today)
issue_date = calendar.advance(evaluationDate,-1,
ql.Years)
maturity_date = ql.Date(22,3,2052)
settlementDays=1
settlementDate=today+1
dayCount=ql.ActualActual(ql.ActualActual.ISMA)
ql.Settings.instance().setEvaluationDate(evaluationDate)
#input values to be checked
inputYield=-0.014546100
inputCleanPrice=179.03
inputDuration=34.52
inputAccrued=0.0483425414365
#create schedule
fixedSchedule = ql.Schedule(issue_date,maturity_date,ql.Period(ql.Semiannual),calendar,ql.Unadjusted,ql.Unadjusted,ql.DateGeneration.Backward,False)
#create bond
fixed_rate_bond = ql.FixedRateBond(settlementDays,faceValue,fixedSchedule,coupons,dayCount,businessConvention,100,issue_date,calendar,exCouponPeriod,calendarCoupon,businessConventionCoupon,False)
#curve
ts_curve=ql.FlatForward(evaluationDate, inputYield, dayCount, ql.Compounded, ql.Semiannual)
#curve handle
ts_handle = ql.YieldTermStructureHandle(ts_curve)
#create bond engine
bond_engine = ql.DiscountingBondEngine(ts_handle)#set bond engine
#set pricing engine
fixed_rate_bond.setPricingEngine(bond_engine)
#calculate yield from clean price
#calculations using BondFunctions
bf_gry=ql.BondFunctions.bondYield(fixed_rate_bond,inputCleanPrice,dayCount,ql.Compounded,ql.Semiannual,settlementDate)
bf_cp=ql.BondFunctions.cleanPrice(fixed_rate_bond,bf_gry,dayCount,ql.Compounded,ql.Semiannual,settlementDate)
bf_ai=ql.BondFunctions.accruedAmount(fixed_rate_bond,settlementDate)
bf_md=ql.BondFunctions.duration(fixed_rate_bond,bf_gry,dayCount,ql.Compounded,ql.Semiannual,ql.Duration.Modified,settlementDate)
#Calculations using bond
b_gry=fixed_rate_bond.bondYield(dayCount,ql.Compounded,ql.Semiannual)
b_cp=fixed_rate_bond.cleanPrice()
b_ai=fixed_rate_bond.accruedAmount()
print
"Clean Price Expected :",inputCleanPrice
print
"Clean Price BondFunctions.cleanPrice() :",round(bf_cp,2)
print
"Clean Price bond.cleanPrice() :",round(b_cp,2)
print
"Difference :",b_cp-bf_cp
print
""
print
"Yield Expected :",round(inputYield,8)
print
"Yield BondFunctions.BondYield() :",round(bf_gry,8)
print
"Yield bond.bondYield() :",round(b_gry,8)
print
"Difference :",bf_gry-b_gry
print
""
print
"Duration Expected :",inputDuration
print
"Duration BondFunctions.duration() :",round(bf_md,2)
print
"Accrued Expected :", inputAccrued
print
"Accrued BondFunctions.accruedAmount() :", bf_ai
print
"Accrued bond.accruedAmount() :", b_ai
------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Hi Nick,
I am not sure but it might have to do with the way your flat forward yield term structure and the yield-based bond functions measure the time between the settlement date and the cashflow payment dates effectively for the calculation of forward discount factors. The former does dayCounter(evalDate, cashflowDate) - dayCounter(evalDate, settlementDate) whereas the latter dayCounter(settlementDate, cashflowDate) which might be different in your case for one or several cashflows. I don't know the exact market convention for this but apparently the latter is matching in your case and the former is slightly wrong. You could check by replacing the day counter with another one for which the two expressions above are identical for all cashflow dates (of course the results get "wrong" then, but should be identical for the different methods then). Kind Regards Peter On 6 December 2016 at 19:39, Nick Pierce <[hidden email]> wrote: > Hi > > I’m very new to Quantlib - about a week in (quite amazed so far!), but I > have managed to get some bonds working in Python 2.7, using SWIG. QL v 1.9. > > I have set up a bond and the schedule is correct. When I calculate the clean > price by inputting a yield (using BondFunctions.cleanPrice(…pass in yield)), > this is correct (and yield from clean price), accrued is also correct and > duration is fine as yield is not far out. > > However, when I attach a yield curve (flat forward) with the same yield, I > get a fractionally different / price yield calc. Dates in the schedule are > unadjusted, not modified following. > > Results: > > Clean Price Expected : 179.03 > Clean Price BondFunctions.cleanPrice() : 179.03 > Clean Price bond.cleanPrice() : 179.09 > Difference : 0.0626431535518 > > Yield Expected : -0.0145461 > Yield BondFunctions.BondYield() : -0.0145461 > Yield bond.bondYield() : -0.01455624 > Difference : 1.01382016107e-05 > > Duration Expected : 34.52 > Duration BondFunctions.duration() : 34.52 > Accrued Expected : 0.0483425414365 > Accrued BondFunctions.accruedAmount() : 0.0483425414365 > Accrued bond.accruedAmount() : 0.0483425414365 > Program ended with exit code: 0 > > As you can see the calculated clean price from bond.cleanPrice() is 6 cents > different (incorrect)..and it’s been driving me mad!! The yield is different > from the 5th decimal place. > > This is a real bond and the input numbers have been verified using a manual > calc in excel, they match the officially published numbers from the UK debt > management office (negative yield > is the real yield for an inflation linked gilt, i.e. the quote of 179.03 is > the market quote (clean) for the UKTI 52’s, before the index ratio is > applied). > > In this example the price is the closing price for 30th Nov and settlement > date is 1st Dec, 2016. > > If anyone can point out where I have gone wrong it would be much > appreciated. > > Code is below. > > Many thanks > > Nick > > import QuantLib as ql > import datetime as dt > calendar = ql.UnitedKingdom() > coupons=[0.0025] > exCouponPeriod=ql.Period(6,ql.Days) > calendarCoupon=ql.UnitedKingdom() > faceValue=100 > businessConvention = ql.Unadjusted > businessConventionCoupon=ql.Unadjusted > > today = ql.Date(30,11,2016) > evaluationDate = calendar.adjust(today) > issue_date = calendar.advance(evaluationDate,-1, ql.Years) > maturity_date = ql.Date(22,3,2052) > > settlementDays=1 > settlementDate=today+1 > dayCount=ql.ActualActual(ql.ActualActual.ISMA) > > > ql.Settings.instance().setEvaluationDate(evaluationDate) > > #input values to be checked > inputYield=-0.014546100 > inputCleanPrice=179.03 > inputDuration=34.52 > inputAccrued=0.0483425414365 > > > #create schedule > fixedSchedule = > ql.Schedule(issue_date,maturity_date,ql.Period(ql.Semiannual),calendar,ql.Unadjusted,ql.Unadjusted,ql.DateGeneration.Backward,False) > > #create bond > fixed_rate_bond = > ql.FixedRateBond(settlementDays,faceValue,fixedSchedule,coupons,dayCount,businessConvention,100,issue_date,calendar,exCouponPeriod,calendarCoupon,businessConventionCoupon,False) > > #curve > ts_curve=ql.FlatForward(evaluationDate, inputYield, dayCount, ql.Compounded, > ql.Semiannual) > > #curve handle > ts_handle = ql.YieldTermStructureHandle(ts_curve) > > #create bond engine > bond_engine = ql.DiscountingBondEngine(ts_handle)#set bond engine > > #set pricing engine > fixed_rate_bond.setPricingEngine(bond_engine) > > #calculate yield from clean price > #calculations using BondFunctions > bf_gry=ql.BondFunctions.bondYield(fixed_rate_bond,inputCleanPrice,dayCount,ql.Compounded,ql.Semiannual,settlementDate) > bf_cp=ql.BondFunctions.cleanPrice(fixed_rate_bond,bf_gry,dayCount,ql.Compounded,ql.Semiannual,settlementDate) > bf_ai=ql.BondFunctions.accruedAmount(fixed_rate_bond,settlementDate) > bf_md=ql.BondFunctions.duration(fixed_rate_bond,bf_gry,dayCount,ql.Compounded,ql.Semiannual,ql.Duration.Modified,settlementDate) > > #Calculations using bond > b_gry=fixed_rate_bond.bondYield(dayCount,ql.Compounded,ql.Semiannual) > b_cp=fixed_rate_bond.cleanPrice() > b_ai=fixed_rate_bond.accruedAmount() > > print "Clean Price Expected :",inputCleanPrice > print "Clean Price BondFunctions.cleanPrice() :",round(bf_cp,2) > print "Clean Price bond.cleanPrice() :",round(b_cp,2) > print "Difference :",b_cp-bf_cp > print "" > print "Yield Expected :",round(inputYield,8) > print "Yield BondFunctions.BondYield() :",round(bf_gry,8) > print "Yield bond.bondYield() :",round(b_gry,8) > print "Difference :",bf_gry-b_gry > print "" > > print "Duration Expected :",inputDuration > print "Duration BondFunctions.duration() :",round(bf_md,2) > > print "Accrued Expected :", inputAccrued > print "Accrued BondFunctions.accruedAmount() :", bf_ai > print "Accrued bond.accruedAmount() :", b_ai > > ------------------------------------------------------------------------------ > Developer Access Program for Intel Xeon Phi Processors > Access to Intel Xeon Phi processor-based developer platforms. > With one year of Intel Parallel Studio XE. > Training and support from Colfax. > Order your platform today.http://sdm.link/xeonphi > _______________________________________________ > QuantLib-users mailing list > [hidden email] > https://lists.sourceforge.net/lists/listinfo/quantlib-users > ------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Hi Peter
Thanks for the quick response, much appreciated.
Yes I think you are correct…
using
dayCount=ql.ActualActual(ql.ActualActual.ISDA)
Clean Price Expected : 167.05
Clean Price BondFunctions.cleanPrice() : 167.05
Clean Price bond.cleanPrice() : 167.06
Difference : 0.00866617031875
Yield Expected : 0.01835696
Yield BondFunctions.BondYield() : 0.01836077
Yield bond.bondYield() : 0.01835696
Difference : 3.81175596394e-06
using
dayCount=ql.Thirty360()
Clean Price Expected : 167.05
Clean Price BondFunctions.cleanPrice() : 167.05
Clean Price bond.cleanPrice() : 167.06
Difference : 0.00935696611168
Yield Expected : 0.01835696
Yield BondFunctions.BondYield() : 0.01836097
Yield bond.bondYield() : 0.01835696
Difference : 4.01397533715e-06
So in short the BondFunctions call is unaffected by the change in dayCounter and correct in all cases.
However, the bond.cleanPrice() changes with the change in dayCounter and is ironically closer (less than 1 cent out) versus the original 6 cents.
..so my next daft question is…
Is there anyway to fix this and should they be consistent???
Thanks again.
Regards
Nick
------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
I cannot test it right now, but the issue might be solved if instead of ts_curve=ql.FlatForward( you would use ts_curve=ql.FlatForward(settlementDate, inputYield, dayCount, ql.Compounded, ql.Semiannual) as the latter is the proper way to set the time origin in your case On Tue, Dec 6, 2016 at 9:17 PM, Nick Pierce <[hidden email]> wrote:
------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Thanks Ferdinando
I made the change as you suggested, but alas it didn’t solve the problem, so I took a different approach and ran the same bond set up with a number of day counts in the curve and bond, as opposed to just the curve (using settlementDate).
The results are below (different bond, but generically the same problem):
The bond.cleanPrice() and BondFunctions.cleanPrice() return the same value in every case except the ActualAcual.ISMA.
I also changed the schedule generation and bond set up to be modified following instead of unadjusted, just in case it was a weekend problem, but it made no difference.
Does this suggest there is a bug in the ISMA generation of a curve??
Any further help much appreciated.
Thanks
Nick
The result we are expecting is 179.03 (round to 2 d.p), using ISMA.
Actual/Actual (ISMA) day counter
bond.cleanPrice()= 179.099809062 <<<<this is the
wrong one
BondFunctions.cleanPrice()= 179.029981553 <<<<correct when rounded to 2 decimal places
Actual/Actual (ISDA) day counter
bond.cleanPrice()= 179.028406531
BondFunctions.cleanPrice()= 179.028406531
Actual/365 (Fixed) day counter
bond.cleanPrice()= 179.086145534
BondFunctions.cleanPrice()= 179.086145534
Actual/365 (NL) day counter
bond.cleanPrice()= 179.023736844
BondFunctions.cleanPrice()= 179.023736844
30/360 (Bond Basis) day counter
bond.cleanPrice()= 179.034435254
BondFunctions.cleanPrice()= 179.034435254
------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
I didn’t test anything either, but I guess the difference comes ultimately from the telescopic construction of discount factors in Cashflows::npv(…, yield, …) which is different from the reference date based discount factor calculation in YieldTermStructure for day counters with non-additive year fractions, look into cashflows.cpp L834:
Real CashFlows::npv(const Leg& leg, const InterestRate& y, ... ) { ... /* !!! telescopic construction of discount factors !!! */ DiscountFactor b = y.discountFactor(lastDate, couponDate, refStartDate, refEndDate); discount *= b; lastDate = couponDate; ...
------------------------------------------------------------------------------ Developer Access Program for Intel Xeon Phi Processors Access to Intel Xeon Phi processor-based developer platforms. With one year of Intel Parallel Studio XE. Training and support from Colfax. Order your platform today.http://sdm.link/xeonphi _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
I'm in the didn't-test crowd too, but I think it might be the same issue as <http://quant.stackexchange.com/questions/12707/pricing-a-fixedratebond-in-quantlib-yield-vs-termstructure>. There's a longer explanation there. Luigi On Mon, Dec 12, 2016 at 8:57 AM Peter Caspers <[hidden email]> wrote:
------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, SlashDot.org! http://sdm.link/slashdot _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Free forum by Nabble | Edit this page |