Small differences in bond YTM calculation inside odd coupon period


#1

Hi,

I’m seeing some differences between Strata’s YTM calculation and Bloomberg’s where the pricing date is inside an odd coupon period. It’s not a huge difference in most cases, but could be bigger if the stub was longer.

For this bond: DE0001104644

Issue date: 13/5/2016
First coupon: 15/6/2017
Maturity: 15/06/2018
Coupon: 0%
Coupon freq: Annual
First period type : Long First
Pricing Date: 30/08/2016

For a bond price of 101.102, Bloomberg has a YTM of -0.610%, Strata has -0.633% . Not a big difference at all, but I still wanted to explain it, as it was bang on for most other bonds.

I can replicate Bloomberg’s calculation, and the difference is how the pvAtFirstCoupon is discounted to the pricing date.

Strata considers the ‘factorToNextCoupon’ to be the factor for the whole first coupon period (very roughly (nextCoupon-settlement)/(nextCoupon-issue)) = 0.7262951900298692

But Bloomberg ignores the little stub at the start of the first coupon period and uses a denominator of what would have been the regular period (((nextCoupon-settlement)/(nextCoupon-“15/6/2016”) = 0.7917808219

I’m struggling to find much information on what the standard actually is, but using this quasi coupon period also agrees with our existing risk system.

Can you see if the problem is with the code I’m using (below), with strata or with Bloomberg?

Thanks,
Adam

Here’s the Strata 1.0.0 code I’m using:

FixedCouponBond bond = FixedCouponBond.builder()
.currency(Currency.EUR)
.securityId(SecurityId.of(“ISIN”, “DE0001104644”))
.dayCount(DayCounts.ACT_ACT_ICMA)
.fixedRate(0)
.notional(1)
.accrualSchedule(
PeriodicSchedule.builder()
.startDate(LocalDate.of(2016, 5, 13))
.endDate(LocalDate.of(2018, 6, 15))
.firstRegularStartDate(LocalDate.of(2017, 6, 15))
.frequency(Frequency.P12M)
.businessDayAdjustment(BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, HolidayCalendarIds.EUTA))
.build()
)
.yieldConvention(FixedCouponBondYieldConvention.DE_BONDS)
.legalEntityId(StandardId.of(“LegalEntity”, “DUMMY”))
.settlementDateOffset(DaysAdjustment.ofBusinessDays(2, HolidayCalendarIds.EUTA))
.exCouponPeriod(DaysAdjustment.NONE)
.build();

ResolvedFixedCouponBond resolvedBond = bond.resolve(ReferenceData.standard());

double yield = DiscountingFixedCouponBondProductPricer.DEFAULT.yieldFromDirtyPrice(resolvedBond, LocalDate.of(2016, 8, 30), 1.01102);
System.out.println(yield); // -0.006328584959046614, expected -0.006097996

double price = DiscountingFixedCouponBondProductPricer.DEFAULT.dirtyPriceFromYield(resolvedBond, LocalDate.of(2016, 8, 30), -0.00609799584105163);
System.out.println(price); // 1.0106151133505177, expected 1.01102


#2

A solution is to use firstRegularStartDate in PeriodicSchedule to specify the start date of regular period schedule (i.e., the end of the initial stub). Thus PeriodicSchedule in your FixedCouponBond build is

PeriodicSchedule.builder()
.startDate(LocalDate.of(2016, 5, 13))
.endDate(LocalDate.of(2018, 6, 15))
.firstRegularStartDate(LocalDate.of(2016, 6, 15))
.frequency(Frequency.P12M)
....

With this change, the resulting numbers in your test are -0.0060979958… and 1.01102, as expected.