Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a4d1560
Pricer-2592 fix yield on bonds with short/long last coupon
Jun 21, 2024
49fd21f
Pricer-2592 add boolean isRegular to FixedCouponBondPaymentPeriod
Jul 2, 2024
45cd52c
Pricer-2592 fill isRegular boolean to FixedCouponBondPaymentPeriod
Jul 2, 2024
e0c8e77
Pricer-2592 use isRegular to compute the power of factorOnPeriod
Jul 2, 2024
4774178
Pricer-2592 add test act_act_isda, act_act_isma
Jul 2, 2024
1f10eda
Bump org.junit:junit-bom from 5.10.2 to 5.11.2 (#2674)
dependabot[bot] Oct 14, 2024
bda8f10
Overnight Rate Cap Floor Model (#2677)
yukiiwashita Oct 16, 2024
5f86c7a
Adding Inauguration Day to MXMC (#2678)
Andras1022 Oct 16, 2024
c9ff500
[maven-release-plugin] prepare release v2.12.42
opengammacibot Oct 16, 2024
6cf8752
[maven-release-plugin] prepare for next development iteration
opengammacibot Oct 16, 2024
1f44cce
Overnight CapFloor CSV plugin (#2679)
skitini Oct 16, 2024
d494271
Overnight rate in arrears cap floor pricers (#2680)
yukiiwashita Oct 16, 2024
bd1d98a
[maven-release-plugin] prepare release v2.12.43
opengammacibot Oct 17, 2024
56505f6
[maven-release-plugin] prepare for next development iteration
opengammacibot Oct 17, 2024
1809ab8
Adding Inauguration Day to MXMC (#2682)
Andras1022 Oct 28, 2024
43b9579
[maven-release-plugin] prepare release v2.12.44
opengammacibot Oct 28, 2024
1b23c7a
[maven-release-plugin] prepare for next development iteration
opengammacibot Oct 28, 2024
4be4dfd
Adding XSFA Exchange (#2684)
MichaelRol Nov 21, 2024
259bb01
[maven-release-plugin] prepare release v2.12.45
opengammacibot Nov 21, 2024
4fb3073
[maven-release-plugin] prepare for next development iteration
opengammacibot Nov 21, 2024
b2d5472
Adding Dia Nacional de Zumbi e da Consciência Negra to BRBD (#2685)
Andras1022 Nov 28, 2024
06613ed
[maven-release-plugin] prepare release v2.12.46
opengammacibot Nov 28, 2024
b402edf
[maven-release-plugin] prepare for next development iteration
opengammacibot Nov 28, 2024
45bed8b
Create OvernightOvernightSwapConventions (#2686)
brianweller89 Dec 19, 2024
4fef1c3
Add US National Day of Mourning (#2692)
jodastephen Jan 5, 2025
d2cd86a
Bump com.google.guava:guava from 32.1.3-jre to 33.4.0-jre (#2688)
dependabot[bot] Jan 5, 2025
ac1fc5a
Bump org.junit:junit-bom from 5.11.2 to 5.11.4 (#2690)
dependabot[bot] Jan 5, 2025
440dd04
Bump org.assertj:assertj-core from 3.25.3 to 3.27.0 (#2689)
dependabot[bot] Jan 5, 2025
a3405ed
Merge branch 'ritonglue-Pricer-2592_fix_irregular_dates_yield'
jodastephen Jan 5, 2025
3168e0f
* Adjust new property name
jodastephen Jan 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static com.opengamma.strata.product.bond.FixedCouponBondYieldConvention.US_STREET;

import java.time.LocalDate;
import java.util.List;
import java.util.function.Function;

import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -554,9 +555,9 @@ public double dirtyPriceFromYield(ResolvedFixedCouponBond bond, LocalDate settle
if (nCoupon == 1) {
if (yieldConv.equals(US_STREET) || yieldConv.equals(DE_BONDS)) {
FixedCouponBondPaymentPeriod payment = payments.get(payments.size() - 1);
double eventsPerYear = bond.getFrequency().eventsPerYear();
return (1d + payment.getFixedRate() * payment.getYearFraction()) /
(1d + factorToNextCoupon(bond, settlementDate) * yield /
((double) bond.getFrequency().eventsPerYear()));
(1d + factorToNextCoupon(bond, settlementDate) * yield / eventsPerYear);
}
}
if ((yieldConv.equals(US_STREET)) || (yieldConv.equals(GB_BUMP_DMO)) || (yieldConv.equals(DE_BONDS))) {
Expand Down Expand Up @@ -592,11 +593,11 @@ public ValueDerivatives dirtyPriceFromYieldAd(ResolvedFixedCouponBond bond, Loca
if (nCoupon == 1) {
if (yieldConv.equals(US_STREET) || yieldConv.equals(DE_BONDS)) {
FixedCouponBondPaymentPeriod payment = payments.get(payments.size() - 1);
double df = (1d + factorToNextCoupon(bond, settlementDate) * yield /
((double) bond.getFrequency().eventsPerYear()));
double eventsPerYear = bond.getFrequency().eventsPerYear();
double df = (1d + factorToNextCoupon(bond, settlementDate) * yield / eventsPerYear);
double price = (1d + payment.getFixedRate() * payment.getYearFraction()) / df;
double yieldBar = -(1d + payment.getFixedRate() * payment.getYearFraction()) /
(df * df) * factorToNextCoupon(bond, settlementDate) / ((double) bond.getFrequency().eventsPerYear());
(df * df) * factorToNextCoupon(bond, settlementDate) / eventsPerYear;
return ValueDerivatives.of(price, DoubleArray.of(yieldBar));
}
}
Expand Down Expand Up @@ -624,20 +625,32 @@ private double dirtyPriceFromYieldStandard(
LocalDate settlementDate,
double yield) {

int nbCoupon = bond.getPeriodicPayments().size();
double factorOnPeriod = 1 + yield / ((double) bond.getFrequency().eventsPerYear());
List<FixedCouponBondPaymentPeriod> periodicPayments = bond.getPeriodicPayments();
double eventsPerYear = bond.getFrequency().eventsPerYear();
double factorOnPeriod = 1 + yield / eventsPerYear;
double fixedRate = bond.getFixedRate();
double pvAtFirstCoupon = 0;
int pow = 0;
for (int loopcpn = 0; loopcpn < nbCoupon; loopcpn++) {
FixedCouponBondPaymentPeriod period = bond.getPeriodicPayments().get(loopcpn);
double factor = 1;
boolean first = true;
for (FixedCouponBondPaymentPeriod period : periodicPayments) {
if ((period.hasExCouponPeriod() && !settlementDate.isAfter(period.getDetachmentDate())) ||
(!period.hasExCouponPeriod() && period.getPaymentDate().isAfter(settlementDate))) {
pvAtFirstCoupon += fixedRate * period.getYearFraction() / Math.pow(factorOnPeriod, pow);
++pow;
double yearFraction = period.getYearFraction();
if (first) {
first = false;
factor = 1;
} else {
if (period.isRegular()) {
factor *= factorOnPeriod;
} else {
factor *= Math.pow(factorOnPeriod, yearFraction * eventsPerYear);
}
}
pvAtFirstCoupon += yearFraction / factor;
}
}
pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1);
pvAtFirstCoupon *= fixedRate;
pvAtFirstCoupon += 1d / factor;
return pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNextCoupon(bond, settlementDate));
}

Expand All @@ -648,7 +661,8 @@ private ValueDerivatives dirtyPriceFromYieldStandardAd(
double yield) {

int nbCoupon = bond.getPeriodicPayments().size();
double factorOnPeriod = 1 + yield / ((double) bond.getFrequency().eventsPerYear());
double eventsPerYear = bond.getFrequency().eventsPerYear();
double factorOnPeriod = 1 + yield / eventsPerYear;
double fixedRate = bond.getFixedRate();
double pvAtFirstCoupon = 0;
int pow = 0;
Expand Down Expand Up @@ -681,7 +695,7 @@ private ValueDerivatives dirtyPriceFromYieldStandardAd(
}
}
factorOnPeriodBar += -factorNextCoupon * Math.pow(factorOnPeriod, -factorNextCoupon - 1.0) * priceAfterBar;
double yieldBar = 1.0d / ((double) bond.getFrequency().eventsPerYear()) * factorOnPeriodBar;
double yieldBar = 1.0d / eventsPerYear * factorOnPeriodBar;
return ValueDerivatives.of(price, DoubleArray.of(yieldBar));
}

Expand Down Expand Up @@ -958,12 +972,12 @@ public double macaulayDurationFromYield(ResolvedFixedCouponBond bond, LocalDate
int nCoupon = payments.size() - couponIndex(payments, settlementDate);
FixedCouponBondYieldConvention yieldConv = bond.getYieldConvention();
if ((yieldConv.equals(US_STREET)) && (nCoupon == 1)) {
return factorToNextCoupon(bond, settlementDate) /
bond.getFrequency().eventsPerYear();
double eventsPerYear = bond.getFrequency().eventsPerYear();
return factorToNextCoupon(bond, settlementDate) / eventsPerYear;
}
if ((yieldConv.equals(US_STREET)) || (yieldConv.equals(GB_BUMP_DMO)) || (yieldConv.equals(DE_BONDS))) {
return modifiedDurationFromYield(bond, settlementDate, yield) *
(1d + yield / bond.getFrequency().eventsPerYear());
double eventsPerYear = bond.getFrequency().eventsPerYear();
return modifiedDurationFromYield(bond, settlementDate, yield) * (1d + yield / eventsPerYear);
}
throw new UnsupportedOperationException("The convention " + yieldConv.name() + " is not supported.");
}
Expand Down Expand Up @@ -1056,7 +1070,8 @@ private double factorToNextCoupon(ResolvedFixedCouponBond bond, LocalDate settle
int couponIndex = couponIndex(bond.getPeriodicPayments(), settlementDate);
double factorSpot = accruedYearFraction(bond, settlementDate);
double factorPeriod = bond.getPeriodicPayments().get(couponIndex).getYearFraction();
return (factorPeriod - factorSpot) * ((double) bond.getFrequency().eventsPerYear());
double eventsPerYear = bond.getFrequency().eventsPerYear();
return (factorPeriod - factorSpot) * eventsPerYear;
}

private int couponIndex(ImmutableList<FixedCouponBondPaymentPeriod> list, LocalDate date) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.assertj.core.api.Assertions.offset;

import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.List;

Expand Down Expand Up @@ -115,6 +116,27 @@ public class DiscountingFixedCouponBondProductPricerTest {
.yieldConvention(YIELD_CONVENTION)
.build()
.resolve(REF_DATA);
private static final PeriodicSchedule PERIOD_SCHEDULE_WITH_FIRST_REGULAR_START_DATE = PeriodicSchedule.builder()
.startDate(LocalDate.of(2021, Month.JUNE, 30))
.firstRegularStartDate(LocalDate.of(2021, Month.OCTOBER, 15))
.endDate(LocalDate.of(2026, Month.JUNE, 30))
.frequency(Frequency.P6M)
.businessDayAdjustment(BUSINESS_ADJUST)
.stubConvention(StubConvention.SMART_FINAL)
.build();
private static final ResolvedFixedCouponBond PRODUCT_WITH_FIRST_REGULAR_START_DATE = FixedCouponBond.builder()
.securityId(SECURITY_ID)
.dayCount(DayCounts.THIRTY_E_360_ISDA)
.fixedRate(0.085)
.legalEntityId(ISSUER_ID)
.currency(EUR)
.notional(1_000_000)
.accrualSchedule(PERIOD_SCHEDULE_WITH_FIRST_REGULAR_START_DATE)
.settlementDateOffset(DaysAdjustment.ofBusinessDays(2, EUR_CALENDAR))
.yieldConvention(FixedCouponBondYieldConvention.DE_BONDS)
.exCouponPeriod(EX_COUPON)
.build()
.resolve(REF_DATA);

// rates provider
private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
Expand Down Expand Up @@ -151,6 +173,64 @@ public class DiscountingFixedCouponBondProductPricerTest {
private static final RatesFiniteDifferenceSensitivityCalculator FD_CAL =
new RatesFiniteDifferenceSensitivityCalculator(EPS);

@Test
public void test_yield_act_act_isda() {
PeriodicSchedule period = PeriodicSchedule.builder()
.startDate(LocalDate.of(2021, Month.JUNE, 30))
.endDate(LocalDate.of(2026, Month.JUNE, 30))
.frequency(Frequency.P6M)
.businessDayAdjustment(BUSINESS_ADJUST)
.build();
ResolvedFixedCouponBond bond = FixedCouponBond.builder()
.securityId(SECURITY_ID)
.dayCount(DayCounts.ACT_ACT_ISDA)
.fixedRate(0.085)
.legalEntityId(ISSUER_ID)
.currency(EUR)
.notional(100)
.accrualSchedule(period)
.settlementDateOffset(DaysAdjustment.ofBusinessDays(2, EUR_CALENDAR))
.yieldConvention(FixedCouponBondYieldConvention.DE_BONDS)
.exCouponPeriod(EX_COUPON)
.build()
.resolve(REF_DATA);
double cleanPrice = 1.05;
LocalDate settlementDate = period.getStartDate();
double dirtyPrice = PRICER.dirtyPriceFromCleanPrice(bond, settlementDate, cleanPrice);
assertThat(dirtyPrice).isCloseTo(cleanPrice, offset(TOL)); // 2.x.
double yield = PRICER.yieldFromDirtyPrice(bond, settlementDate, dirtyPrice);
assertThat(yield).isCloseTo(0.07286881667273096, offset(TOL)); // 2.x.œœ
}

@Test
public void test_yield_act_act_icma() {
PeriodicSchedule period = PeriodicSchedule.builder()
.startDate(LocalDate.of(2021, Month.JUNE, 30))
.endDate(LocalDate.of(2026, Month.JUNE, 30))
.frequency(Frequency.P6M)
.businessDayAdjustment(BUSINESS_ADJUST)
.build();
ResolvedFixedCouponBond bond = FixedCouponBond.builder()
.securityId(SECURITY_ID)
.dayCount(DayCounts.ACT_ACT_ICMA)
.fixedRate(0.085)
.legalEntityId(ISSUER_ID)
.currency(EUR)
.notional(100)
.accrualSchedule(period)
.settlementDateOffset(DaysAdjustment.ofBusinessDays(2, EUR_CALENDAR))
.yieldConvention(FixedCouponBondYieldConvention.DE_BONDS)
.exCouponPeriod(EX_COUPON)
.build()
.resolve(REF_DATA);
double cleanPrice = 1.05;
LocalDate settlementDate = period.getStartDate();
double dirtyPrice = PRICER.dirtyPriceFromCleanPrice(bond, settlementDate, cleanPrice);
assertThat(dirtyPrice).isCloseTo(cleanPrice, offset(TOL)); // 2.x.
double yield = PRICER.yieldFromDirtyPrice(bond, settlementDate, dirtyPrice);
assertThat(yield).isCloseTo(0.07288818170674201, offset(TOL)); // 2.x.œœ
}

//-------------------------------------------------------------------------
@Test
public void test_presentValue() {
Expand Down Expand Up @@ -330,6 +410,21 @@ public void test_dirtyPriceFromCleanPrice_ukNewIssue() {
assertThat(cleanPrice).isCloseTo(dirtyPrice - accruedInterest / NOTIONAL, offset(NOTIONAL * TOL));
}

@Test
public void test_yieldWithFirstRegularStartDate() {
double cleanPrice = 1.0009;
LocalDate settlementDate = LocalDate.of(2023, Month.JUNE, 6);
double dirtyPrice = PRICER.dirtyPriceFromCleanPrice(PRODUCT_WITH_FIRST_REGULAR_START_DATE, settlementDate, cleanPrice);
assertThat(dirtyPrice).isCloseTo(1.0129416666666667, offset(TOL)); // 2.x.
double yield = PRICER.yieldFromDirtyPrice(PRODUCT_WITH_FIRST_REGULAR_START_DATE, settlementDate, dirtyPrice);
assertThat(yield).isCloseTo(0.08465593560577835, offset(TOL));
settlementDate = LocalDate.of(2024, Month.APRIL, 26);
double dirtyPriceRbt = PRICER.dirtyPriceFromYield(PRODUCT_WITH_FIRST_REGULAR_START_DATE, settlementDate, yield);
double cleanPriceRbt = PRICER.cleanPriceFromDirtyPrice(PRODUCT_WITH_FIRST_REGULAR_START_DATE, settlementDate, dirtyPriceRbt);
double delta = (cleanPriceRbt - cleanPrice) * PRODUCT_WITH_FIRST_REGULAR_START_DATE.getNotional();
assertThat(delta).isCloseTo(-100.27459341377387, offset(TOL));
}

//-------------------------------------------------------------------------
@Test
public void test_zSpreadFromCurvesAndPV_continuous() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public ResolvedFixedCouponBond resolve(ReferenceData refData) {
.currency(currency)
.fixedRate(fixedRate)
.yearFraction(unadjustedPeriod.yearFraction(dayCount, unadjustedSchedule))
.regular(period.isRegular(accrualSchedule.getFrequency(), accrualSchedule.calculatedRollConvention()))
.build());
}
ImmutableList<FixedCouponBondPaymentPeriod> periodicPayments = accrualPeriods.build();
Expand Down
Loading