From a4d1560fd82d72d334a9fb820d7757e22047b139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JEAN-PROST=20Fr=C3=A9d=C3=A9ric?= Date: Fri, 21 Jun 2024 21:30:48 +0200 Subject: [PATCH 01/29] Pricer-2592 fix yield on bonds with short/long last coupon --- ...scountingFixedCouponBondProductPricer.java | 26 +++++++++---- ...ntingFixedCouponBondProductPricerTest.java | 37 +++++++++++++++++++ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java index da3c60c614..10a7221002 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java @@ -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; @@ -624,20 +625,29 @@ private double dirtyPriceFromYieldStandard( LocalDate settlementDate, double yield) { - int nbCoupon = bond.getPeriodicPayments().size(); - double factorOnPeriod = 1 + yield / ((double) bond.getFrequency().eventsPerYear()); + List periodicPayments = bond.getPeriodicPayments(); + int eventsPerYear = bond.getFrequency().eventsPerYear(); + double factorOnPeriod = 1 + yield / ((double) 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 { + double delta = yearFraction * eventsPerYear; + factor *= Math.pow(factorOnPeriod, delta); + } + pvAtFirstCoupon += yearFraction / factor; } } - pvAtFirstCoupon += 1d / Math.pow(factorOnPeriod, pow - 1); + pvAtFirstCoupon *= fixedRate; + pvAtFirstCoupon += 1d / factor; return pvAtFirstCoupon * Math.pow(factorOnPeriod, -factorToNextCoupon(bond, settlementDate)); } diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java index 8d8716529c..a5b86e48b2 100644 --- a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java @@ -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; @@ -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; @@ -330,6 +352,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() { From 49fd21f69d3c0ba4457e0f381123672e844f6eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JEAN-PROST=20Fr=C3=A9d=C3=A9ric?= Date: Tue, 2 Jul 2024 21:17:55 +0200 Subject: [PATCH 02/29] Pricer-2592 add boolean isRegular to FixedCouponBondPaymentPeriod --- .../bond/FixedCouponBondPaymentPeriod.java | 84 +++++++++++++++++-- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java index 611952b06a..8ed478b96c 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java @@ -122,6 +122,15 @@ public final class FixedCouponBondPaymentPeriod */ @PropertyDefinition(validate = "ArgChecker.notNegative") private final double yearFraction; + /** + * Indicates if the is period is regular + *

+ * The regular status of the period + *

+ * It true the a full coupon is paid. Otherwise the period is shorter/longer + */ + @PropertyDefinition + private final boolean isRegular; //------------------------------------------------------------------------- // could use @ImmutablePreBuild and @ImmutableValidate but faster inline @@ -135,7 +144,8 @@ private FixedCouponBondPaymentPeriod( LocalDate unadjustedEndDate, LocalDate detachmentDate, double fixedRate, - double yearFraction) { + double yearFraction, + boolean isRegular) { this.currency = ArgChecker.notNull(currency, "currency"); this.notional = notional; this.startDate = ArgChecker.notNull(startDate, "startDate"); @@ -145,6 +155,7 @@ private FixedCouponBondPaymentPeriod( this.detachmentDate = firstNonNull(detachmentDate, endDate); this.fixedRate = fixedRate; this.yearFraction = yearFraction; + this.isRegular = isRegular; // check for unadjusted must be after firstNonNull ArgChecker.inOrderNotEqual(startDate, endDate, "startDate", "endDate"); ArgChecker.inOrderNotEqual( @@ -339,6 +350,19 @@ public double getYearFraction() { return yearFraction; } + //----------------------------------------------------------------------- + /** + * Gets indicates if the is period is regular + *

+ * The regular status of the period + *

+ * It true the a full coupon is paid. Otherwise the period is shorter/longer + * @return the value of the property + */ + public boolean isIsRegular() { + return isRegular; + } + //----------------------------------------------------------------------- /** * Returns a builder that allows this bean to be mutated. @@ -363,7 +387,8 @@ public boolean equals(Object obj) { JodaBeanUtils.equal(unadjustedEndDate, other.unadjustedEndDate) && JodaBeanUtils.equal(detachmentDate, other.detachmentDate) && JodaBeanUtils.equal(fixedRate, other.fixedRate) && - JodaBeanUtils.equal(yearFraction, other.yearFraction); + JodaBeanUtils.equal(yearFraction, other.yearFraction) && + (isRegular == other.isRegular); } return false; } @@ -380,12 +405,13 @@ public int hashCode() { hash = hash * 31 + JodaBeanUtils.hashCode(detachmentDate); hash = hash * 31 + JodaBeanUtils.hashCode(fixedRate); hash = hash * 31 + JodaBeanUtils.hashCode(yearFraction); + hash = hash * 31 + JodaBeanUtils.hashCode(isRegular); return hash; } @Override public String toString() { - StringBuilder buf = new StringBuilder(320); + StringBuilder buf = new StringBuilder(352); buf.append("FixedCouponBondPaymentPeriod{"); buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); @@ -395,7 +421,8 @@ public String toString() { buf.append("unadjustedEndDate").append('=').append(JodaBeanUtils.toString(unadjustedEndDate)).append(',').append(' '); buf.append("detachmentDate").append('=').append(JodaBeanUtils.toString(detachmentDate)).append(',').append(' '); buf.append("fixedRate").append('=').append(JodaBeanUtils.toString(fixedRate)).append(',').append(' '); - buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)); + buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)).append(',').append(' '); + buf.append("isRegular").append('=').append(JodaBeanUtils.toString(isRegular)); buf.append('}'); return buf.toString(); } @@ -455,6 +482,11 @@ public static final class Meta extends DirectMetaBean { */ private final MetaProperty yearFraction = DirectMetaProperty.ofImmutable( this, "yearFraction", FixedCouponBondPaymentPeriod.class, Double.TYPE); + /** + * The meta-property for the {@code isRegular} property. + */ + private final MetaProperty isRegular = DirectMetaProperty.ofImmutable( + this, "isRegular", FixedCouponBondPaymentPeriod.class, Boolean.TYPE); /** * The meta-properties. */ @@ -468,7 +500,8 @@ public static final class Meta extends DirectMetaBean { "unadjustedEndDate", "detachmentDate", "fixedRate", - "yearFraction"); + "yearFraction", + "isRegular"); /** * Restricted constructor. @@ -497,6 +530,8 @@ protected MetaProperty metaPropertyGet(String propertyName) { return fixedRate; case -1731780257: // yearFraction return yearFraction; + case 506685202: // isRegular + return isRegular; } return super.metaPropertyGet(propertyName); } @@ -589,6 +624,14 @@ public MetaProperty yearFraction() { return yearFraction; } + /** + * The meta-property for the {@code isRegular} property. + * @return the meta-property, not null + */ + public MetaProperty isRegular() { + return isRegular; + } + //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { @@ -611,6 +654,8 @@ protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { return ((FixedCouponBondPaymentPeriod) bean).getFixedRate(); case -1731780257: // yearFraction return ((FixedCouponBondPaymentPeriod) bean).getYearFraction(); + case 506685202: // isRegular + return ((FixedCouponBondPaymentPeriod) bean).isIsRegular(); } return super.propertyGet(bean, propertyName, quiet); } @@ -641,6 +686,7 @@ public static final class Builder extends DirectFieldsBeanBuilder + * The regular status of the period + *

+ * It true the a full coupon is paid. Otherwise the period is shorter/longer + * @param isRegular the new value + * @return this, for chaining, not null + */ + public Builder isRegular(boolean isRegular) { + this.isRegular = isRegular; + return this; + } + //----------------------------------------------------------------------- @Override public String toString() { - StringBuilder buf = new StringBuilder(320); + StringBuilder buf = new StringBuilder(352); buf.append("FixedCouponBondPaymentPeriod.Builder{"); buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); @@ -893,7 +960,8 @@ public String toString() { buf.append("unadjustedEndDate").append('=').append(JodaBeanUtils.toString(unadjustedEndDate)).append(',').append(' '); buf.append("detachmentDate").append('=').append(JodaBeanUtils.toString(detachmentDate)).append(',').append(' '); buf.append("fixedRate").append('=').append(JodaBeanUtils.toString(fixedRate)).append(',').append(' '); - buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)); + buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)).append(',').append(' '); + buf.append("isRegular").append('=').append(JodaBeanUtils.toString(isRegular)); buf.append('}'); return buf.toString(); } From 45cd52c83da58c16007113fcdd2d04bb5392b551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JEAN-PROST=20Fr=C3=A9d=C3=A9ric?= Date: Tue, 2 Jul 2024 21:19:18 +0200 Subject: [PATCH 03/29] Pricer-2592 fill isRegular boolean to FixedCouponBondPaymentPeriod --- .../java/com/opengamma/strata/product/bond/FixedCouponBond.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java index cb5635c88c..9c2ab84f5e 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java @@ -189,6 +189,7 @@ public ResolvedFixedCouponBond resolve(ReferenceData refData) { .currency(currency) .fixedRate(fixedRate) .yearFraction(unadjustedPeriod.yearFraction(dayCount, unadjustedSchedule)) + .isRegular(period.isRegular(accrualSchedule.getFrequency(), accrualSchedule.calculatedRollConvention())) .build()); } ImmutableList periodicPayments = accrualPeriods.build(); From e0c8e775f0b40c67e5bb06e7791ac036f42214e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JEAN-PROST=20Fr=C3=A9d=C3=A9ric?= Date: Tue, 2 Jul 2024 21:28:29 +0200 Subject: [PATCH 04/29] Pricer-2592 use isRegular to compute the power of factorOnPeriod --- .../bond/DiscountingFixedCouponBondProductPricer.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java index 10a7221002..440f5887ce 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java @@ -640,8 +640,11 @@ private double dirtyPriceFromYieldStandard( first = false; factor = 1; } else { - double delta = yearFraction * eventsPerYear; - factor *= Math.pow(factorOnPeriod, delta); + if (period.isIsRegular()) { + factor *= factorOnPeriod; + } else { + factor *= Math.pow(factorOnPeriod, yearFraction * eventsPerYear); + } } pvAtFirstCoupon += yearFraction / factor; } From 477417889a6c7acd80436dc86e42db7f8ace1743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JEAN-PROST=20Fr=C3=A9d=C3=A9ric?= Date: Tue, 2 Jul 2024 22:03:57 +0200 Subject: [PATCH 05/29] Pricer-2592 add test act_act_isda, act_act_isma --- ...ntingFixedCouponBondProductPricerTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java index a5b86e48b2..a2ca53a3e3 100644 --- a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java @@ -173,6 +173,63 @@ 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() { From 1f10edae6fa9b54d5a8b3217cf2d87b7558b818a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 08:49:45 +0000 Subject: [PATCH 06/29] Bump org.junit:junit-bom from 5.10.2 to 5.11.2 (#2674) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.11.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.11.2) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- modules/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pom.xml b/modules/pom.xml index 7641c4d6eb..f71ddad74f 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -771,7 +771,7 @@ 2.2.3 2.11.0 ${joda-beans.version} - 5.10.2 + 5.11.2 4.9.0 1.7.36 From bda8f109fd945ada397ba4c4ecb3c4a6bc85f291 Mon Sep 17 00:00:00 2001 From: Yuki Iwashita Date: Wed, 16 Oct 2024 09:55:48 +0100 Subject: [PATCH 07/29] Overnight Rate Cap Floor Model (#2677) * overnight cap product * tests * javadoc * line * clean * white spaces * failing test for accrual --- .../opengamma/strata/product/ProductType.java | 7 + .../capfloor/OvernightInArrearsCapFloor.java | 398 ++++++++ .../OvernightInArrearsCapFloorLeg.java | 888 ++++++++++++++++++ .../OvernightInArrearsCapFloorTrade.java | 516 ++++++++++ ...vernightInArrearsCapletFloorletPeriod.java | 2 +- .../ResolvedOvernightInArrearsCapFloor.java | 420 +++++++++ ...ResolvedOvernightInArrearsCapFloorLeg.java | 468 +++++++++ ...solvedOvernightInArrearsCapFloorTrade.java | 449 +++++++++ .../OvernightInArrearsCapFloorLegTest.java | 305 ++++++ .../OvernightInArrearsCapFloorTest.java | 167 ++++ .../OvernightInArrearsCapFloorTradeTest.java | 194 ++++ ...lvedOvernightInArrearsCapFloorLegTest.java | 186 ++++ ...esolvedOvernightInArrearsCapFloorTest.java | 176 ++++ ...edOvernightInArrearsCapFloorTradeTest.java | 80 ++ 14 files changed, 4255 insertions(+), 1 deletion(-) create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java diff --git a/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java b/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java index 270f7fd3ce..5d37f1aa5d 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/ProductType.java @@ -16,6 +16,7 @@ import com.opengamma.strata.product.bond.CapitalIndexedBond; import com.opengamma.strata.product.bond.FixedCouponBond; import com.opengamma.strata.product.capfloor.IborCapFloor; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloor; import com.opengamma.strata.product.cms.Cms; import com.opengamma.strata.product.credit.Cds; import com.opengamma.strata.product.credit.CdsIndex; @@ -110,6 +111,12 @@ public final class ProductType * A {@link IborCapFloor}. */ public static final ProductType IBOR_CAP_FLOOR = ProductType.of("IborCapFloor", "Cap/Floor"); + /** + * A {@link OvernightInArrearsCapFloor}. + */ + public static final ProductType OVERNIGHT_IN_ARREARS_CAP_FLOOR = ProductType.of( + "OvernightInArrearsCapFloor", + "Overnight In Arrears Cap/Floor"); /** * A {@link IborFuture}. */ diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java new file mode 100644 index 0000000000..e47673bb9d --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloor.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.BeanBuilder; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableValidator; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; +import org.joda.beans.impl.direct.DirectPrivateBeanBuilder; + +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.Resolvable; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.Index; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.Product; +import com.opengamma.strata.product.swap.SwapLeg; + +/** + * An overnight rate in arrears cap/floor product. + *

+ * The overnight rate in arrears cap/floor product consists of two legs, a cap/floor leg and a pay leg. + * The cap/floor leg involves a set of call/put options on successive compounded overnight index rates. + * The pay leg is any swap leg from a standard interest rate swap. + * The pay leg is absent for typical overnight rate in arrears cap/floor products, + * with the premium paid upfront instead, as defined in {@link OvernightInArrearsCapFloorTrade}. + */ +@BeanDefinition(builderScope = "private") +public final class OvernightInArrearsCapFloor + implements Product, Resolvable, ImmutableBean, Serializable { + + /** + * The cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are caplets or floorlets. + */ + @PropertyDefinition(validate = "notNull") + private final OvernightInArrearsCapFloorLeg capFloorLeg; + /** + * The optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. + * Instead the premium is paid upfront. + */ + @PropertyDefinition(get = "optional") + private final SwapLeg payLeg; + + //------------------------------------------------------------------------- + /** + * Obtains an instance from a cap/floor leg with no pay leg. + *

+ * The pay leg is absent in the resulting cap/floor. + * + * @param capFloorLeg the cap/floor leg + * @return the cap/floor + */ + public static OvernightInArrearsCapFloor of(OvernightInArrearsCapFloorLeg capFloorLeg) { + return new OvernightInArrearsCapFloor(capFloorLeg, null); + } + + /** + * Obtains an instance from a cap/floor leg and a pay leg. + * + * @param capFloorLeg the cap/floor leg + * @param payLeg the pay leg + * @return the cap/floor + */ + public static OvernightInArrearsCapFloor of(OvernightInArrearsCapFloorLeg capFloorLeg, SwapLeg payLeg) { + return new OvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + @ImmutableValidator + private void validate() { + if (payLeg != null) { + ArgChecker.isFalse( + payLeg.getPayReceive().equals(capFloorLeg.getPayReceive()), + "Legs must have different Pay/Receive flag, but both were {}", payLeg.getPayReceive()); + } + } + + //------------------------------------------------------------------------- + @Override + public ImmutableSet allPaymentCurrencies() { + if (payLeg == null) { + return ImmutableSet.of(capFloorLeg.getCurrency()); + } + return ImmutableSet.of(capFloorLeg.getCurrency(), payLeg.getCurrency()); + } + + @Override + public ImmutableSet allCurrencies() { + if (payLeg == null) { + return ImmutableSet.of(capFloorLeg.getCurrency()); + } + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCurrency()); + builder.addAll(payLeg.allCurrencies()); + return builder.build(); + } + + /** + * Returns the set of indices referred to by the cap/floor. + *

+ * A cap/floor will typically refer to one index, such as 'GBP-SONIA'. + * Calling this method will return the complete list of indices. + * + * @return the set of indices referred to by this cap/floor + */ + public ImmutableSet allIndices() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCalculation().getIndex()); + if (payLeg != null) { + payLeg.collectIndices(builder); + } + return builder.build(); + } + + //------------------------------------------------------------------------- + @Override + public ResolvedOvernightInArrearsCapFloor resolve(ReferenceData refData) { + if (payLeg == null) { + return ResolvedOvernightInArrearsCapFloor.of(capFloorLeg.resolve(refData)); + } + return ResolvedOvernightInArrearsCapFloor.of(capFloorLeg.resolve(refData), payLeg.resolve(refData)); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloor}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloor.Meta meta() { + return OvernightInArrearsCapFloor.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloor.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + private OvernightInArrearsCapFloor( + OvernightInArrearsCapFloorLeg capFloorLeg, + SwapLeg payLeg) { + JodaBeanUtils.notNull(capFloorLeg, "capFloorLeg"); + this.capFloorLeg = capFloorLeg; + this.payLeg = payLeg; + validate(); + } + + @Override + public OvernightInArrearsCapFloor.Meta metaBean() { + return OvernightInArrearsCapFloor.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are caplets or floorlets. + * @return the value of the property, not null + */ + public OvernightInArrearsCapFloorLeg getCapFloorLeg() { + return capFloorLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. + * Instead the premium is paid upfront. + * @return the optional value of the property, not null + */ + public Optional getPayLeg() { + return Optional.ofNullable(payLeg); + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloor other = (OvernightInArrearsCapFloor) obj; + return JodaBeanUtils.equal(capFloorLeg, other.capFloorLeg) && + JodaBeanUtils.equal(payLeg, other.payLeg); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(capFloorLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(payLeg); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("OvernightInArrearsCapFloor{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloor}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code capFloorLeg} property. + */ + private final MetaProperty capFloorLeg = DirectMetaProperty.ofImmutable( + this, "capFloorLeg", OvernightInArrearsCapFloor.class, OvernightInArrearsCapFloorLeg.class); + /** + * The meta-property for the {@code payLeg} property. + */ + private final MetaProperty payLeg = DirectMetaProperty.ofImmutable( + this, "payLeg", OvernightInArrearsCapFloor.class, SwapLeg.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "capFloorLeg", + "payLeg"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public BeanBuilder builder() { + return new OvernightInArrearsCapFloor.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloor.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code capFloorLeg} property. + * @return the meta-property, not null + */ + public MetaProperty capFloorLeg() { + return capFloorLeg; + } + + /** + * The meta-property for the {@code payLeg} property. + * @return the meta-property, not null + */ + public MetaProperty payLeg() { + return payLeg; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return ((OvernightInArrearsCapFloor) bean).getCapFloorLeg(); + case -995239866: // payLeg + return ((OvernightInArrearsCapFloor) bean).payLeg; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloor}. + */ + private static final class Builder extends DirectPrivateBeanBuilder { + + private OvernightInArrearsCapFloorLeg capFloorLeg; + private SwapLeg payLeg; + + /** + * Restricted constructor. + */ + private Builder() { + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + this.capFloorLeg = (OvernightInArrearsCapFloorLeg) newValue; + break; + case -995239866: // payLeg + this.payLeg = (SwapLeg) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public OvernightInArrearsCapFloor build() { + return new OvernightInArrearsCapFloor( + capFloorLeg, + payLeg); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("OvernightInArrearsCapFloor.Builder{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java new file mode 100644 index 0000000000..af60401eb9 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLeg.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableList; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.Resolvable; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.date.AdjustableDate; +import com.opengamma.strata.basics.date.DateAdjuster; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.schedule.Schedule; +import com.opengamma.strata.basics.schedule.SchedulePeriod; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.collect.array.DoubleArray; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.OvernightAccrualMethod; +import com.opengamma.strata.product.swap.OvernightRateCalculation; +import com.opengamma.strata.product.swap.RateAccrualPeriod; + +/** + * An overnight rate in arrears cap/floor leg of a cap/floor product. + *

+ * This defines a single cap/floor leg for an overnight rate in arrears cap/floor product. + * The cap/floor instruments are defined as a set of call/put options on successive compounded overnight index rates. + *

+ * The periodic payments in the resolved leg are caplets or floorlets depending on the data in this leg. + * The {@code capSchedule} field is used to represent strike values of individual caplets, + * whereas {@code floorSchedule} is used to represent strike values of individual floorlets. + * Either {@code capSchedule} or {@code floorSchedule} must be present, and not both. + */ +@BeanDefinition +public final class OvernightInArrearsCapFloorLeg + implements Resolvable, ImmutableBean, Serializable { + + /** + * Whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + */ + @PropertyDefinition(validate = "notNull") + private final PayReceive payReceive; + /** + * The periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + */ + @PropertyDefinition(validate = "notNull") + private final PeriodicSchedule paymentSchedule; + /** + * The offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + */ + @PropertyDefinition(validate = "notNull") + private final DaysAdjustment paymentDateOffset; + /** + * The currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + */ + @PropertyDefinition(validate = "notNull") + private final Currency currency; + /** + * The notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + */ + @PropertyDefinition(validate = "notNull") + private final ValueSchedule notional; + /** + * The interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + */ + @PropertyDefinition(validate = "notNull") + private final OvernightRateCalculation calculation; + /** + * The cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + */ + @PropertyDefinition(get = "optional") + private final ValueSchedule capSchedule; + /** + * The floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + */ + @PropertyDefinition(get = "optional") + private final ValueSchedule floorSchedule; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.paymentDateOffset(DaysAdjustment.NONE); + } + + @ImmutableConstructor + private OvernightInArrearsCapFloorLeg( + PayReceive payReceive, + PeriodicSchedule paymentSchedule, + DaysAdjustment paymentDateOffset, + Currency currency, + ValueSchedule notional, + OvernightRateCalculation calculation, + ValueSchedule capSchedule, + ValueSchedule floorSchedule) { + + this.payReceive = ArgChecker.notNull(payReceive, "payReceive"); + this.paymentSchedule = ArgChecker.notNull(paymentSchedule, "paymentSchedule"); + this.paymentDateOffset = ArgChecker.notNull(paymentDateOffset, "paymentDateOffset"); + this.currency = currency != null ? currency : calculation.getIndex().getCurrency(); + this.notional = notional; + this.calculation = ArgChecker.notNull(calculation, "calculation"); + this.capSchedule = capSchedule; + this.floorSchedule = floorSchedule; + ArgChecker.isTrue(!this.getPaymentSchedule().getStubConvention().isPresent() || + this.getPaymentSchedule().getStubConvention().get().equals(StubConvention.NONE), "Stub period is not allowed"); + ArgChecker.isFalse(this.getCapSchedule().isPresent() == this.getFloorSchedule().isPresent(), + "One of cap schedule and floor schedule should be empty"); + ArgChecker.isTrue(this.getCalculation().getAccrualMethod().equals(OvernightAccrualMethod.COMPOUNDED), + "Overnight accrual method should be compounded"); + } + + //------------------------------------------------------------------------- + /** + * Gets the accrual start date of the leg. + *

+ * This is the first accrual date in the leg, often known as the effective date. + * + * @return the start date of the leg + */ + public AdjustableDate getStartDate() { + return paymentSchedule.calculatedStartDate(); + } + + /** + * Gets the accrual end date of the leg. + *

+ * This is the last accrual date in the leg, often known as the termination date. + * + * @return the end date of the leg + */ + public AdjustableDate getEndDate() { + return paymentSchedule.calculatedEndDate(); + } + + /** + * Gets the overnight index. + *

+ * The rate to be paid is based on this index + * It will be a well known market index such as 'GBP-SONIA'. + * + * @return the overnight index + */ + public OvernightIndex getIndex() { + return calculation.getIndex(); + } + + //------------------------------------------------------------------------- + @Override + public ResolvedOvernightInArrearsCapFloorLeg resolve(ReferenceData refData) { + Schedule adjustedSchedule = paymentSchedule.createSchedule(refData); + DoubleArray cap = getCapSchedule().isPresent() ? capSchedule.resolveValues(adjustedSchedule) : null; + DoubleArray floor = getFloorSchedule().isPresent() ? floorSchedule.resolveValues(adjustedSchedule) : null; + DoubleArray notionals = notional.resolveValues(adjustedSchedule); + List accrualPeriods = calculation.createAccrualPeriods(adjustedSchedule, adjustedSchedule, refData); + DateAdjuster paymentDateAdjuster = paymentDateOffset.resolve(refData); + ImmutableList.Builder periodsBuild = ImmutableList.builder(); + for (int i = 0; i < adjustedSchedule.size(); i++) { + SchedulePeriod period = adjustedSchedule.getPeriod(i); + LocalDate paymentDate = paymentDateAdjuster.adjust(period.getEndDate()); + double signedNotional = payReceive.normalize(notionals.get(i)); + periodsBuild.add(OvernightInArrearsCapletFloorletPeriod.builder() + .unadjustedStartDate(period.getUnadjustedStartDate()) + .unadjustedEndDate(period.getUnadjustedEndDate()) + .startDate(period.getStartDate()) + .endDate(period.getEndDate()) + .overnightRate((OvernightCompoundedRateComputation) accrualPeriods.get(i).getRateComputation()) // arg checked + .paymentDate(paymentDate) + .notional(signedNotional) + .currency(currency) + .yearFraction(period.yearFraction(calculation.getDayCount(), adjustedSchedule)) + .caplet(cap != null ? cap.get(i) : null) + .floorlet(floor != null ? floor.get(i) : null) + .build()); + } + return ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periodsBuild.build()) + .payReceive(payReceive) + .build(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorLeg}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloorLeg.Meta meta() { + return OvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloorLeg.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapFloorLeg.Builder builder() { + return new OvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public OvernightInArrearsCapFloorLeg.Meta metaBean() { + return OvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + * @return the value of the property, not null + */ + public PayReceive getPayReceive() { + return payReceive; + } + + //----------------------------------------------------------------------- + /** + * Gets the periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + * @return the value of the property, not null + */ + public PeriodicSchedule getPaymentSchedule() { + return paymentSchedule; + } + + //----------------------------------------------------------------------- + /** + * Gets the offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + * @return the value of the property, not null + */ + public DaysAdjustment getPaymentDateOffset() { + return paymentDateOffset; + } + + //----------------------------------------------------------------------- + /** + * Gets the currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + * @return the value of the property, not null + */ + public Currency getCurrency() { + return currency; + } + + //----------------------------------------------------------------------- + /** + * Gets the notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + * @return the value of the property, not null + */ + public ValueSchedule getNotional() { + return notional; + } + + //----------------------------------------------------------------------- + /** + * Gets the interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + * @return the value of the property, not null + */ + public OvernightRateCalculation getCalculation() { + return calculation; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + * @return the optional value of the property, not null + */ + public Optional getCapSchedule() { + return Optional.ofNullable(capSchedule); + } + + //----------------------------------------------------------------------- + /** + * Gets the floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + * @return the optional value of the property, not null + */ + public Optional getFloorSchedule() { + return Optional.ofNullable(floorSchedule); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloorLeg other = (OvernightInArrearsCapFloorLeg) obj; + return JodaBeanUtils.equal(payReceive, other.payReceive) && + JodaBeanUtils.equal(paymentSchedule, other.paymentSchedule) && + JodaBeanUtils.equal(paymentDateOffset, other.paymentDateOffset) && + JodaBeanUtils.equal(currency, other.currency) && + JodaBeanUtils.equal(notional, other.notional) && + JodaBeanUtils.equal(calculation, other.calculation) && + JodaBeanUtils.equal(capSchedule, other.capSchedule) && + JodaBeanUtils.equal(floorSchedule, other.floorSchedule); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(payReceive); + hash = hash * 31 + JodaBeanUtils.hashCode(paymentSchedule); + hash = hash * 31 + JodaBeanUtils.hashCode(paymentDateOffset); + hash = hash * 31 + JodaBeanUtils.hashCode(currency); + hash = hash * 31 + JodaBeanUtils.hashCode(notional); + hash = hash * 31 + JodaBeanUtils.hashCode(calculation); + hash = hash * 31 + JodaBeanUtils.hashCode(capSchedule); + hash = hash * 31 + JodaBeanUtils.hashCode(floorSchedule); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(288); + buf.append("OvernightInArrearsCapFloorLeg{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("paymentSchedule").append('=').append(JodaBeanUtils.toString(paymentSchedule)).append(',').append(' '); + buf.append("paymentDateOffset").append('=').append(JodaBeanUtils.toString(paymentDateOffset)).append(',').append(' '); + buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); + buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); + buf.append("calculation").append('=').append(JodaBeanUtils.toString(calculation)).append(',').append(' '); + buf.append("capSchedule").append('=').append(JodaBeanUtils.toString(capSchedule)).append(',').append(' '); + buf.append("floorSchedule").append('=').append(JodaBeanUtils.toString(floorSchedule)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorLeg}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code payReceive} property. + */ + private final MetaProperty payReceive = DirectMetaProperty.ofImmutable( + this, "payReceive", OvernightInArrearsCapFloorLeg.class, PayReceive.class); + /** + * The meta-property for the {@code paymentSchedule} property. + */ + private final MetaProperty paymentSchedule = DirectMetaProperty.ofImmutable( + this, "paymentSchedule", OvernightInArrearsCapFloorLeg.class, PeriodicSchedule.class); + /** + * The meta-property for the {@code paymentDateOffset} property. + */ + private final MetaProperty paymentDateOffset = DirectMetaProperty.ofImmutable( + this, "paymentDateOffset", OvernightInArrearsCapFloorLeg.class, DaysAdjustment.class); + /** + * The meta-property for the {@code currency} property. + */ + private final MetaProperty currency = DirectMetaProperty.ofImmutable( + this, "currency", OvernightInArrearsCapFloorLeg.class, Currency.class); + /** + * The meta-property for the {@code notional} property. + */ + private final MetaProperty notional = DirectMetaProperty.ofImmutable( + this, "notional", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-property for the {@code calculation} property. + */ + private final MetaProperty calculation = DirectMetaProperty.ofImmutable( + this, "calculation", OvernightInArrearsCapFloorLeg.class, OvernightRateCalculation.class); + /** + * The meta-property for the {@code capSchedule} property. + */ + private final MetaProperty capSchedule = DirectMetaProperty.ofImmutable( + this, "capSchedule", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-property for the {@code floorSchedule} property. + */ + private final MetaProperty floorSchedule = DirectMetaProperty.ofImmutable( + this, "floorSchedule", OvernightInArrearsCapFloorLeg.class, ValueSchedule.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "payReceive", + "paymentSchedule", + "paymentDateOffset", + "currency", + "notional", + "calculation", + "capSchedule", + "floorSchedule"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case -1499086147: // paymentSchedule + return paymentSchedule; + case -716438393: // paymentDateOffset + return paymentDateOffset; + case 575402001: // currency + return currency; + case 1585636160: // notional + return notional; + case -934682935: // calculation + return calculation; + case -596212599: // capSchedule + return capSchedule; + case -1562227005: // floorSchedule + return floorSchedule; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapFloorLeg.Builder builder() { + return new OvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloorLeg.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code payReceive} property. + * @return the meta-property, not null + */ + public MetaProperty payReceive() { + return payReceive; + } + + /** + * The meta-property for the {@code paymentSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty paymentSchedule() { + return paymentSchedule; + } + + /** + * The meta-property for the {@code paymentDateOffset} property. + * @return the meta-property, not null + */ + public MetaProperty paymentDateOffset() { + return paymentDateOffset; + } + + /** + * The meta-property for the {@code currency} property. + * @return the meta-property, not null + */ + public MetaProperty currency() { + return currency; + } + + /** + * The meta-property for the {@code notional} property. + * @return the meta-property, not null + */ + public MetaProperty notional() { + return notional; + } + + /** + * The meta-property for the {@code calculation} property. + * @return the meta-property, not null + */ + public MetaProperty calculation() { + return calculation; + } + + /** + * The meta-property for the {@code capSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty capSchedule() { + return capSchedule; + } + + /** + * The meta-property for the {@code floorSchedule} property. + * @return the meta-property, not null + */ + public MetaProperty floorSchedule() { + return floorSchedule; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return ((OvernightInArrearsCapFloorLeg) bean).getPayReceive(); + case -1499086147: // paymentSchedule + return ((OvernightInArrearsCapFloorLeg) bean).getPaymentSchedule(); + case -716438393: // paymentDateOffset + return ((OvernightInArrearsCapFloorLeg) bean).getPaymentDateOffset(); + case 575402001: // currency + return ((OvernightInArrearsCapFloorLeg) bean).getCurrency(); + case 1585636160: // notional + return ((OvernightInArrearsCapFloorLeg) bean).getNotional(); + case -934682935: // calculation + return ((OvernightInArrearsCapFloorLeg) bean).getCalculation(); + case -596212599: // capSchedule + return ((OvernightInArrearsCapFloorLeg) bean).capSchedule; + case -1562227005: // floorSchedule + return ((OvernightInArrearsCapFloorLeg) bean).floorSchedule; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloorLeg}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private PayReceive payReceive; + private PeriodicSchedule paymentSchedule; + private DaysAdjustment paymentDateOffset; + private Currency currency; + private ValueSchedule notional; + private OvernightRateCalculation calculation; + private ValueSchedule capSchedule; + private ValueSchedule floorSchedule; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapFloorLeg beanToCopy) { + this.payReceive = beanToCopy.getPayReceive(); + this.paymentSchedule = beanToCopy.getPaymentSchedule(); + this.paymentDateOffset = beanToCopy.getPaymentDateOffset(); + this.currency = beanToCopy.getCurrency(); + this.notional = beanToCopy.getNotional(); + this.calculation = beanToCopy.getCalculation(); + this.capSchedule = beanToCopy.capSchedule; + this.floorSchedule = beanToCopy.floorSchedule; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case -1499086147: // paymentSchedule + return paymentSchedule; + case -716438393: // paymentDateOffset + return paymentDateOffset; + case 575402001: // currency + return currency; + case 1585636160: // notional + return notional; + case -934682935: // calculation + return calculation; + case -596212599: // capSchedule + return capSchedule; + case -1562227005: // floorSchedule + return floorSchedule; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + this.payReceive = (PayReceive) newValue; + break; + case -1499086147: // paymentSchedule + this.paymentSchedule = (PeriodicSchedule) newValue; + break; + case -716438393: // paymentDateOffset + this.paymentDateOffset = (DaysAdjustment) newValue; + break; + case 575402001: // currency + this.currency = (Currency) newValue; + break; + case 1585636160: // notional + this.notional = (ValueSchedule) newValue; + break; + case -934682935: // calculation + this.calculation = (OvernightRateCalculation) newValue; + break; + case -596212599: // capSchedule + this.capSchedule = (ValueSchedule) newValue; + break; + case -1562227005: // floorSchedule + this.floorSchedule = (ValueSchedule) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapFloorLeg build() { + return new OvernightInArrearsCapFloorLeg( + payReceive, + paymentSchedule, + paymentDateOffset, + currency, + notional, + calculation, + capSchedule, + floorSchedule); + } + + //----------------------------------------------------------------------- + /** + * Sets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + * @param payReceive the new value, not null + * @return this, for chaining, not null + */ + public Builder payReceive(PayReceive payReceive) { + JodaBeanUtils.notNull(payReceive, "payReceive"); + this.payReceive = payReceive; + return this; + } + + /** + * Sets the periodic payment schedule. + *

+ * This is used to define the periodic payment periods. + * These are used directly or indirectly to determine other dates in the leg. + * @param paymentSchedule the new value, not null + * @return this, for chaining, not null + */ + public Builder paymentSchedule(PeriodicSchedule paymentSchedule) { + JodaBeanUtils.notNull(paymentSchedule, "paymentSchedule"); + this.paymentSchedule = paymentSchedule; + return this; + } + + /** + * Sets the offset of payment from the base calculation period date, defaulted to 'None'. + *

+ * The offset is applied to the adjusted end date of each payment period. + * Offset can be based on calendar days or business days. + * @param paymentDateOffset the new value, not null + * @return this, for chaining, not null + */ + public Builder paymentDateOffset(DaysAdjustment paymentDateOffset) { + JodaBeanUtils.notNull(paymentDateOffset, "paymentDateOffset"); + this.paymentDateOffset = paymentDateOffset; + return this; + } + + /** + * Sets the currency of the leg associated with the notional. + *

+ * This is the currency of the leg and the currency that payoff calculation is made in. + * The amounts of the notional are expressed in terms of this currency. + * @param currency the new value, not null + * @return this, for chaining, not null + */ + public Builder currency(Currency currency) { + JodaBeanUtils.notNull(currency, "currency"); + this.currency = currency; + return this; + } + + /** + * Sets the notional amount, must be non-negative. + *

+ * The notional amount applicable during the period. + * The currency of the notional is specified by {@code currency}. + * @param notional the new value, not null + * @return this, for chaining, not null + */ + public Builder notional(ValueSchedule notional) { + JodaBeanUtils.notNull(notional, "notional"); + this.notional = notional; + return this; + } + + /** + * Sets the interest rate accrual calculation. + *

+ * The interest rate accrual is based on overnight index. + * The overnight rate accrual method should be compounded. + * @param calculation the new value, not null + * @return this, for chaining, not null + */ + public Builder calculation(OvernightRateCalculation calculation) { + JodaBeanUtils.notNull(calculation, "calculation"); + this.calculation = calculation; + return this; + } + + /** + * Sets the cap schedule, optional. + *

+ * This defines the strike value of a cap as an initial value and a list of adjustments. + * Thus individual caplets may have different strike values. + * The cap rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a cap, the cap schedule will be absent. + * @param capSchedule the new value + * @return this, for chaining, not null + */ + public Builder capSchedule(ValueSchedule capSchedule) { + this.capSchedule = capSchedule; + return this; + } + + /** + * Sets the floor schedule, optional. + *

+ * This defines the strike value of a floor as an initial value and a list of adjustments. + * Thus individual floorlets may have different strike values. + * The floor rate is only allowed to change at payment period boundaries. + *

+ * If the product is not a floor, the floor schedule will be absent. + * @param floorSchedule the new value + * @return this, for chaining, not null + */ + public Builder floorSchedule(ValueSchedule floorSchedule) { + this.floorSchedule = floorSchedule; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(288); + buf.append("OvernightInArrearsCapFloorLeg.Builder{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("paymentSchedule").append('=').append(JodaBeanUtils.toString(paymentSchedule)).append(',').append(' '); + buf.append("paymentDateOffset").append('=').append(JodaBeanUtils.toString(paymentDateOffset)).append(',').append(' '); + buf.append("currency").append('=').append(JodaBeanUtils.toString(currency)).append(',').append(' '); + buf.append("notional").append('=').append(JodaBeanUtils.toString(notional)).append(',').append(' '); + buf.append("calculation").append('=').append(JodaBeanUtils.toString(calculation)).append(',').append(' '); + buf.append("capSchedule").append('=').append(JodaBeanUtils.toString(capSchedule)).append(',').append(' '); + buf.append("floorSchedule").append('=').append(JodaBeanUtils.toString(floorSchedule)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java new file mode 100644 index 0000000000..e9c195814a --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTrade.java @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.product.PortfolioItemInfo; +import com.opengamma.strata.product.PortfolioItemSummary; +import com.opengamma.strata.product.ProductTrade; +import com.opengamma.strata.product.ProductType; +import com.opengamma.strata.product.ResolvableTrade; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.common.SummarizerUtils; + +/** + * A trade in an overnight rate in arrears cap/floor. + *

+ * An Over-The-Counter (OTC) trade in a {@link OvernightInArrearsCapFloor}. + *

+ * An overnight rate in arrears cap/floor is a financial instrument that provides a set of call/put options on + * successive compounded overnight index rates. + */ +@BeanDefinition +public final class OvernightInArrearsCapFloorTrade + implements ProductTrade, ResolvableTrade, ImmutableBean, Serializable { + + /** + * The additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final TradeInfo info; + /** + * The cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final OvernightInArrearsCapFloor product; + /** + * The optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + */ + @PropertyDefinition(get = "optional") + private final AdjustablePayment premium; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.info = TradeInfo.empty(); + } + + //------------------------------------------------------------------------- + @Override + public OvernightInArrearsCapFloorTrade withInfo(PortfolioItemInfo info) { + return new OvernightInArrearsCapFloorTrade(TradeInfo.from(info), product, premium); + } + + //------------------------------------------------------------------------- + @Override + public PortfolioItemSummary summarize() { + // 5Y USD 2mm Rec Compounded USD-SOFR Cap 1% / Pay Premium : 21Jan17-21Jan22 + StringBuilder buf = new StringBuilder(96); + OvernightInArrearsCapFloorLeg mainLeg = product.getCapFloorLeg(); + buf.append( + SummarizerUtils.datePeriod( + mainLeg.getStartDate().getUnadjusted(), + mainLeg.getEndDate().getUnadjusted())); + buf.append(' '); + buf.append(SummarizerUtils.amount(mainLeg.getCurrency(), mainLeg.getNotional().getInitialValue())); + buf.append(' '); + if (mainLeg.getPayReceive().isReceive()) { + buf.append("Rec "); + summarizeMainLeg(mainLeg, buf); + buf.append(getPremium().isPresent() ? + " / Pay Premium" : + (product.getPayLeg().isPresent() ? " / Pay Periodic" : "")); + } else { + buf.append(getPremium().isPresent() ? + "Rec Premium / Pay " : + (product.getPayLeg().isPresent() ? "Rec Periodic / Pay " : "")); + summarizeMainLeg(mainLeg, buf); + } + buf.append(" : "); + buf.append(SummarizerUtils.dateRange(mainLeg.getStartDate().getUnadjusted(), mainLeg.getEndDate().getUnadjusted())); + return SummarizerUtils.summary( + this, + ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR, + buf.toString(), + mainLeg.getCurrency()); + } + + // summarize the main leg + private void summarizeMainLeg(OvernightInArrearsCapFloorLeg mainLeg, StringBuilder buf) { + buf.append(mainLeg.getCalculation().getAccrualMethod()); + buf.append(' '); + buf.append(mainLeg.getIndex()); + buf.append(' '); + if (mainLeg.getCapSchedule().isPresent()) { + buf.append("Cap "); + buf.append(SummarizerUtils.percent(mainLeg.getCapSchedule().get().getInitialValue())); + } + if (mainLeg.getFloorSchedule().isPresent()) { + buf.append("Floor "); + buf.append(SummarizerUtils.percent(mainLeg.getFloorSchedule().get().getInitialValue())); + } + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade resolve(ReferenceData refData) { + return ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(info) + .product(product.resolve(refData)) + .premium(premium != null ? premium.resolve(refData) : null) + .build(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorTrade}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapFloorTrade.Meta meta() { + return OvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapFloorTrade.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapFloorTrade.Builder builder() { + return new OvernightInArrearsCapFloorTrade.Builder(); + } + + private OvernightInArrearsCapFloorTrade( + TradeInfo info, + OvernightInArrearsCapFloor product, + AdjustablePayment premium) { + JodaBeanUtils.notNull(info, "info"); + JodaBeanUtils.notNull(product, "product"); + this.info = info; + this.product = product; + this.premium = premium; + } + + @Override + public OvernightInArrearsCapFloorTrade.Meta metaBean() { + return OvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @return the value of the property, not null + */ + @Override + public TradeInfo getInfo() { + return info; + } + + //----------------------------------------------------------------------- + /** + * Gets the cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + * @return the value of the property, not null + */ + @Override + public OvernightInArrearsCapFloor getProduct() { + return product; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @return the optional value of the property, not null + */ + public Optional getPremium() { + return Optional.ofNullable(premium); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapFloorTrade other = (OvernightInArrearsCapFloorTrade) obj; + return JodaBeanUtils.equal(info, other.info) && + JodaBeanUtils.equal(product, other.product) && + JodaBeanUtils.equal(premium, other.premium); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(info); + hash = hash * 31 + JodaBeanUtils.hashCode(product); + hash = hash * 31 + JodaBeanUtils.hashCode(premium); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("OvernightInArrearsCapFloorTrade{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapFloorTrade}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code info} property. + */ + private final MetaProperty info = DirectMetaProperty.ofImmutable( + this, "info", OvernightInArrearsCapFloorTrade.class, TradeInfo.class); + /** + * The meta-property for the {@code product} property. + */ + private final MetaProperty product = DirectMetaProperty.ofImmutable( + this, "product", OvernightInArrearsCapFloorTrade.class, OvernightInArrearsCapFloor.class); + /** + * The meta-property for the {@code premium} property. + */ + private final MetaProperty premium = DirectMetaProperty.ofImmutable( + this, "premium", OvernightInArrearsCapFloorTrade.class, AdjustablePayment.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "info", + "product", + "premium"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapFloorTrade.Builder builder() { + return new OvernightInArrearsCapFloorTrade.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapFloorTrade.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code info} property. + * @return the meta-property, not null + */ + public MetaProperty info() { + return info; + } + + /** + * The meta-property for the {@code product} property. + * @return the meta-property, not null + */ + public MetaProperty product() { + return product; + } + + /** + * The meta-property for the {@code premium} property. + * @return the meta-property, not null + */ + public MetaProperty premium() { + return premium; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 3237038: // info + return ((OvernightInArrearsCapFloorTrade) bean).getInfo(); + case -309474065: // product + return ((OvernightInArrearsCapFloorTrade) bean).getProduct(); + case -318452137: // premium + return ((OvernightInArrearsCapFloorTrade) bean).premium; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapFloorTrade}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private TradeInfo info; + private OvernightInArrearsCapFloor product; + private AdjustablePayment premium; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapFloorTrade beanToCopy) { + this.info = beanToCopy.getInfo(); + this.product = beanToCopy.getProduct(); + this.premium = beanToCopy.premium; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 3237038: // info + this.info = (TradeInfo) newValue; + break; + case -309474065: // product + this.product = (OvernightInArrearsCapFloor) newValue; + break; + case -318452137: // premium + this.premium = (AdjustablePayment) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapFloorTrade build() { + return new OvernightInArrearsCapFloorTrade( + info, + product, + premium); + } + + //----------------------------------------------------------------------- + /** + * Sets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @param info the new value, not null + * @return this, for chaining, not null + */ + public Builder info(TradeInfo info) { + JodaBeanUtils.notNull(info, "info"); + this.info = info; + return this; + } + + /** + * Sets the cap/floor product that was agreed when the trade occurred. + *

+ * The product captures the contracted financial details of the trade. + * @param product the new value, not null + * @return this, for chaining, not null + */ + public Builder product(OvernightInArrearsCapFloor product) { + JodaBeanUtils.notNull(product, "product"); + this.product = product; + return this; + } + + /** + * Sets the optional premium of the product. + *

+ * For most cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @param premium the new value + * @return this, for chaining, not null + */ + public Builder premium(AdjustablePayment premium) { + this.premium = premium; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("OvernightInArrearsCapFloorTrade.Builder{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java index 66ed183438..2d33087b80 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapletFloorletPeriod.java @@ -35,7 +35,7 @@ import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; /** - * A period over which an caplet/floorlet on overnight composition in-arrears is paid. + * A period over which a caplet/floorlet on overnight composition in-arrears is paid. *

* The payoff depend on the level of the compounded rate over the period. The option is * of Asian type with the averaging mechanism given by the composition. diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java new file mode 100644 index 0000000000..314cebe2c8 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloor.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.BeanBuilder; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; +import org.joda.beans.impl.direct.DirectPrivateBeanBuilder; + +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.Index; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.ResolvedProduct; +import com.opengamma.strata.product.swap.ResolvedSwapLeg; + +/** + * An overnight rate in arrears cap/floor, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloor} and is an input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloor} + * from a {@code OvernightInArrearsCapFloor} using {@link OvernightInArrearsCapFloor#resolve(ReferenceData)}. + *

+ * A {@code ResolvedOvernightInArrearsCapFloor} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition(builderScope = "private") +public final class ResolvedOvernightInArrearsCapFloor + implements ResolvedProduct, ImmutableBean, Serializable { + + /** + * The overnight rate in arrears cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are overnight rate in arrears caplets or floorlets. + */ + @PropertyDefinition(validate = "notNull") + private final ResolvedOvernightInArrearsCapFloorLeg capFloorLeg; + /** + * The optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. Instead the premium is paid upfront. + */ + @PropertyDefinition(get = "optional") + private final ResolvedSwapLeg payLeg; + /** + * The set of currencies. + */ + private final transient ImmutableSet currencies; // not a property, derived and cached from input data + /** + * The set of indices. + */ + private final transient ImmutableSet indices; // not a property, derived and cached from input data + + //------------------------------------------------------------------------- + /** + * Obtains an instance from a cap/floor leg with no pay leg. + *

+ * The pay leg is absent in the resulting cap/floor. + * + * @param capFloorLeg the cap/floor leg + * @return the cap/floor + */ + public static ResolvedOvernightInArrearsCapFloor of(ResolvedOvernightInArrearsCapFloorLeg capFloorLeg) { + ArgChecker.notNull(capFloorLeg, "capFloorLeg"); + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, null); + } + + /** + * Obtains an instance from a cap/floor leg and a pay leg. + * + * @param capFloorLeg the cap/floor leg + * @param payLeg the pay leg + * @return the cap/floor + */ + public static ResolvedOvernightInArrearsCapFloor of( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ArgChecker.notNull(capFloorLeg, "capFloorLeg"); + ArgChecker.notNull(payLeg, "payLeg"); + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + @ImmutableConstructor + private ResolvedOvernightInArrearsCapFloor( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + JodaBeanUtils.notNull(capFloorLeg, "capFloorLeg"); + if (payLeg != null) { + ArgChecker.isFalse( + payLeg.getPayReceive().equals(capFloorLeg.getPayReceive()), + "Legs must have different Pay/Receive flag, but both were {}", payLeg.getPayReceive()); + } + this.capFloorLeg = capFloorLeg; + this.payLeg = payLeg; + this.currencies = buildCurrencies(capFloorLeg, payLeg); + this.indices = buildIndices(capFloorLeg, payLeg); + } + + // collect the set of currencies + private static ImmutableSet buildCurrencies( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getCurrency()); + if (payLeg != null) { + builder.add(payLeg.getCurrency()); + } + return builder.build(); + } + + // collect the set of indices + private static ImmutableSet buildIndices( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + ResolvedSwapLeg payLeg) { + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(capFloorLeg.getIndex()); + if (payLeg != null) { + payLeg.collectIndices(builder); + } + return builder.build(); + } + + // ensure standard constructor is invoked + private Object readResolve() { + return new ResolvedOvernightInArrearsCapFloor(capFloorLeg, payLeg); + } + + //------------------------------------------------------------------------- + /** + * Returns the set of payment currencies referred to by the cap/floor. + *

+ * This returns the complete set of payment currencies for the cap/floor. + * This will typically return one currency, but could return two. + * + * @return the set of payment currencies referred to by this swap + */ + public ImmutableSet allPaymentCurrencies() { + return currencies; + } + + /** + * Returns the set of indices referred to by the cap/floor. + *

+ * A cap/floor will typically refer to one index, such as 'GBP-SONIA'. + * Calling this method will return the complete list of indices. + * + * @return the set of indices referred to by this cap/floor + */ + public ImmutableSet allIndices() { + return indices; + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloor}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloor.Meta meta() { + return ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + @Override + public ResolvedOvernightInArrearsCapFloor.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloor.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the overnight rate in arrears cap/floor leg of the product. + *

+ * This is associated with periodic payments based on overnight index rate. + * The payments are overnight rate in arrears caplets or floorlets. + * @return the value of the property, not null + */ + public ResolvedOvernightInArrearsCapFloorLeg getCapFloorLeg() { + return capFloorLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional pay leg of the product. + *

+ * These periodic payments are not made for typical cap/floor products. Instead the premium is paid upfront. + * @return the optional value of the property, not null + */ + public Optional getPayLeg() { + return Optional.ofNullable(payLeg); + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloor other = (ResolvedOvernightInArrearsCapFloor) obj; + return JodaBeanUtils.equal(capFloorLeg, other.capFloorLeg) && + JodaBeanUtils.equal(payLeg, other.payLeg); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(capFloorLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(payLeg); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloor{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloor}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code capFloorLeg} property. + */ + private final MetaProperty capFloorLeg = DirectMetaProperty.ofImmutable( + this, "capFloorLeg", ResolvedOvernightInArrearsCapFloor.class, ResolvedOvernightInArrearsCapFloorLeg.class); + /** + * The meta-property for the {@code payLeg} property. + */ + private final MetaProperty payLeg = DirectMetaProperty.ofImmutable( + this, "payLeg", ResolvedOvernightInArrearsCapFloor.class, ResolvedSwapLeg.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "capFloorLeg", + "payLeg"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public BeanBuilder builder() { + return new ResolvedOvernightInArrearsCapFloor.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloor.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code capFloorLeg} property. + * @return the meta-property, not null + */ + public MetaProperty capFloorLeg() { + return capFloorLeg; + } + + /** + * The meta-property for the {@code payLeg} property. + * @return the meta-property, not null + */ + public MetaProperty payLeg() { + return payLeg; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return ((ResolvedOvernightInArrearsCapFloor) bean).getCapFloorLeg(); + case -995239866: // payLeg + return ((ResolvedOvernightInArrearsCapFloor) bean).payLeg; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloor}. + */ + private static final class Builder extends DirectPrivateBeanBuilder { + + private ResolvedOvernightInArrearsCapFloorLeg capFloorLeg; + private ResolvedSwapLeg payLeg; + + /** + * Restricted constructor. + */ + private Builder() { + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + return capFloorLeg; + case -995239866: // payLeg + return payLeg; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 2124672084: // capFloorLeg + this.capFloorLeg = (ResolvedOvernightInArrearsCapFloorLeg) newValue; + break; + case -995239866: // payLeg + this.payLeg = (ResolvedSwapLeg) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloor build() { + return new ResolvedOvernightInArrearsCapFloor( + capFloorLeg, + payLeg); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloor.Builder{"); + buf.append("capFloorLeg").append('=').append(JodaBeanUtils.toString(capFloorLeg)).append(',').append(' '); + buf.append("payLeg").append('=').append(JodaBeanUtils.toString(payLeg)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java new file mode 100644 index 0000000000..fe94ad2f8f --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLeg.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableList; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.common.PayReceive; + +/** + * A cap/floor leg of an overnight rate in arrears cap/floor product, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloorLeg} and is an input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloorLeg} + * from a {@code OvernightInArrearsCapFloorLeg} using {@link OvernightInArrearsCapFloorLeg#resolve(ReferenceData)}. + *

+ * This defines a single leg for an overnight rate in arrears cap/floor product and is formed from a number of periods. + * Each period may be a caplet or floorlet. + * The cap/floor instruments are defined as a set of call/put options on successive compounded overnight index rates. + *

+ * A {@code ResolvedOvernightInArrearsCapFloorLeg} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition +public final class ResolvedOvernightInArrearsCapFloorLeg + implements ImmutableBean, Serializable { + + /** + * Whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + */ + @PropertyDefinition(validate = "notNull") + private final PayReceive payReceive; + /** + * The periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + */ + @PropertyDefinition(validate = "notEmpty") + private final ImmutableList capletFloorletPeriods; + + //------------------------------------------------------------------------- + @ImmutableConstructor + private ResolvedOvernightInArrearsCapFloorLeg( + PayReceive payReceive, + List capletFloorletPeriods) { + + this.payReceive = ArgChecker.notNull(payReceive, "payReceive"); + this.capletFloorletPeriods = ImmutableList.copyOf(capletFloorletPeriods); + Set currencies = + this.capletFloorletPeriods.stream().map(OvernightInArrearsCapletFloorletPeriod::getCurrency).collect(Collectors.toSet()); + ArgChecker.isTrue(currencies.size() == 1, "Leg must have a single currency, found: " + currencies); + Set indices = + this.capletFloorletPeriods.stream().map(OvernightInArrearsCapletFloorletPeriod::getIndex).collect(Collectors.toSet()); + ArgChecker.isTrue(indices.size() == 1, "Leg must have a single overnight index: " + indices); + } + + //------------------------------------------------------------------------- + /** + * Gets the accrual start date of the leg. + *

+ * This is the first accrual date in the leg, often known as the effective date. + * This date has typically been adjusted to be a valid business day. + * + * @return the start date of the leg + */ + public LocalDate getStartDate() { + return capletFloorletPeriods.get(0).getStartDate(); + } + + /** + * Gets the accrual end date of the leg. + *

+ * This is the last accrual date in the leg, often known as the termination date. + * This date has typically been adjusted to be a valid business day. + * + * @return the end date of the leg + */ + public LocalDate getEndDate() { + return capletFloorletPeriods.get(capletFloorletPeriods.size() - 1).getEndDate(); + } + + /** + * Gets the final caplet/floorlet period. + * + * @return the final period + */ + public OvernightInArrearsCapletFloorletPeriod getFinalPeriod() { + return capletFloorletPeriods.get(capletFloorletPeriods.size() - 1); + } + + /** + * Gets the currency of the leg. + *

+ * All periods in the leg will have this currency. + * + * @return the currency + */ + public Currency getCurrency() { + return capletFloorletPeriods.get(0).getCurrency(); + } + + /** + * Gets the overnight index of the leg. + *

+ * All periods in the leg will have this index. + * + * @return the index + */ + public OvernightIndex getIndex() { + return capletFloorletPeriods.get(0).getIndex(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorLeg}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloorLeg.Meta meta() { + return ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static ResolvedOvernightInArrearsCapFloorLeg.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloorLeg.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + * @return the value of the property, not null + */ + public PayReceive getPayReceive() { + return payReceive; + } + + //----------------------------------------------------------------------- + /** + * Gets the periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + * @return the value of the property, not empty + */ + public ImmutableList getCapletFloorletPeriods() { + return capletFloorletPeriods; + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloorLeg other = (ResolvedOvernightInArrearsCapFloorLeg) obj; + return JodaBeanUtils.equal(payReceive, other.payReceive) && + JodaBeanUtils.equal(capletFloorletPeriods, other.capletFloorletPeriods); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(payReceive); + hash = hash * 31 + JodaBeanUtils.hashCode(capletFloorletPeriods); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloorLeg{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("capletFloorletPeriods").append('=').append(JodaBeanUtils.toString(capletFloorletPeriods)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorLeg}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code payReceive} property. + */ + private final MetaProperty payReceive = DirectMetaProperty.ofImmutable( + this, "payReceive", ResolvedOvernightInArrearsCapFloorLeg.class, PayReceive.class); + /** + * The meta-property for the {@code capletFloorletPeriods} property. + */ + @SuppressWarnings({"unchecked", "rawtypes" }) + private final MetaProperty> capletFloorletPeriods = DirectMetaProperty.ofImmutable( + this, "capletFloorletPeriods", ResolvedOvernightInArrearsCapFloorLeg.class, (Class) ImmutableList.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "payReceive", + "capletFloorletPeriods"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case 1504863482: // capletFloorletPeriods + return capletFloorletPeriods; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorLeg.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloorLeg.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code payReceive} property. + * @return the meta-property, not null + */ + public MetaProperty payReceive() { + return payReceive; + } + + /** + * The meta-property for the {@code capletFloorletPeriods} property. + * @return the meta-property, not null + */ + public MetaProperty> capletFloorletPeriods() { + return capletFloorletPeriods; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return ((ResolvedOvernightInArrearsCapFloorLeg) bean).getPayReceive(); + case 1504863482: // capletFloorletPeriods + return ((ResolvedOvernightInArrearsCapFloorLeg) bean).getCapletFloorletPeriods(); + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloorLeg}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private PayReceive payReceive; + private List capletFloorletPeriods = ImmutableList.of(); + + /** + * Restricted constructor. + */ + private Builder() { + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(ResolvedOvernightInArrearsCapFloorLeg beanToCopy) { + this.payReceive = beanToCopy.getPayReceive(); + this.capletFloorletPeriods = beanToCopy.getCapletFloorletPeriods(); + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + return payReceive; + case 1504863482: // capletFloorletPeriods + return capletFloorletPeriods; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @SuppressWarnings("unchecked") + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -885469925: // payReceive + this.payReceive = (PayReceive) newValue; + break; + case 1504863482: // capletFloorletPeriods + this.capletFloorletPeriods = (List) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloorLeg build() { + return new ResolvedOvernightInArrearsCapFloorLeg( + payReceive, + capletFloorletPeriods); + } + + //----------------------------------------------------------------------- + /** + * Sets whether the leg is pay or receive. + *

+ * A value of 'Pay' implies that the resulting amount is paid to the counterparty. + * A value of 'Receive' implies that the resulting amount is received from the counterparty. + *

+ * The value of this flag should match the signs of the payment period notionals. + * @param payReceive the new value, not null + * @return this, for chaining, not null + */ + public Builder payReceive(PayReceive payReceive) { + JodaBeanUtils.notNull(payReceive, "payReceive"); + this.payReceive = payReceive; + return this; + } + + /** + * Sets the periodic payments based on the successive observed values of compounded overnight index rates. + *

+ * Each payment period represents part of the life-time of the leg. + * In most cases, the periods do not overlap. However, since each payment period + * is essentially independent the data model allows overlapping periods. + * @param capletFloorletPeriods the new value, not empty + * @return this, for chaining, not null + */ + public Builder capletFloorletPeriods(List capletFloorletPeriods) { + JodaBeanUtils.notEmpty(capletFloorletPeriods, "capletFloorletPeriods"); + this.capletFloorletPeriods = capletFloorletPeriods; + return this; + } + + /** + * Sets the {@code capletFloorletPeriods} property in the builder + * from an array of objects. + * @param capletFloorletPeriods the new value, not empty + * @return this, for chaining, not null + */ + public Builder capletFloorletPeriods(OvernightInArrearsCapletFloorletPeriod... capletFloorletPeriods) { + return capletFloorletPeriods(ImmutableList.copyOf(capletFloorletPeriods)); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append("ResolvedOvernightInArrearsCapFloorLeg.Builder{"); + buf.append("payReceive").append('=').append(JodaBeanUtils.toString(payReceive)).append(',').append(' '); + buf.append("capletFloorletPeriods").append('=').append(JodaBeanUtils.toString(capletFloorletPeriods)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java new file mode 100644 index 0000000000..25c27db7e7 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTrade.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableDefaults; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.product.ResolvedTrade; +import com.opengamma.strata.product.TradeInfo; + +/** + * A trade in an overnight rate in arrears cap/floor, resolved for pricing. + *

+ * This is the resolved form of {@link OvernightInArrearsCapFloorTrade} and is the primary input to the pricers. + * Applications will typically create a {@code ResolvedOvernightInArrearsCapFloorTrade} from + * a {@code OvernightInArrearsCapFloorTrade} using {@link OvernightInArrearsCapFloorTrade#resolve(ReferenceData)}. + *

+ * A {@code OvernightInArrearsCapFloorTrade} is bound to data that changes over time, such as holiday calendars. + * If the data changes, such as the addition of a new holiday, the resolved form will not be updated. + * Care must be taken when placing the resolved form in a cache or persistence layer. + */ +@BeanDefinition +public final class ResolvedOvernightInArrearsCapFloorTrade + implements ResolvedTrade, ImmutableBean, Serializable { + + /** + * The additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final TradeInfo info; + /** + * The resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final ResolvedOvernightInArrearsCapFloor product; + /** + * The optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + */ + @PropertyDefinition(get = "optional") + private final Payment premium; + + //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.info = TradeInfo.empty(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorTrade}. + * @return the meta-bean, not null + */ + public static ResolvedOvernightInArrearsCapFloorTrade.Meta meta() { + return ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + static { + MetaBean.register(ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static ResolvedOvernightInArrearsCapFloorTrade.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorTrade.Builder(); + } + + private ResolvedOvernightInArrearsCapFloorTrade( + TradeInfo info, + ResolvedOvernightInArrearsCapFloor product, + Payment premium) { + JodaBeanUtils.notNull(info, "info"); + JodaBeanUtils.notNull(product, "product"); + this.info = info; + this.product = product; + this.premium = premium; + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade.Meta metaBean() { + return ResolvedOvernightInArrearsCapFloorTrade.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @return the value of the property, not null + */ + @Override + public TradeInfo getInfo() { + return info; + } + + //----------------------------------------------------------------------- + /** + * Gets the resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + * @return the value of the property, not null + */ + @Override + public ResolvedOvernightInArrearsCapFloor getProduct() { + return product; + } + + //----------------------------------------------------------------------- + /** + * Gets the optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @return the optional value of the property, not null + */ + public Optional getPremium() { + return Optional.ofNullable(premium); + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ResolvedOvernightInArrearsCapFloorTrade other = (ResolvedOvernightInArrearsCapFloorTrade) obj; + return JodaBeanUtils.equal(info, other.info) && + JodaBeanUtils.equal(product, other.product) && + JodaBeanUtils.equal(premium, other.premium); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(info); + hash = hash * 31 + JodaBeanUtils.hashCode(product); + hash = hash * 31 + JodaBeanUtils.hashCode(premium); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("ResolvedOvernightInArrearsCapFloorTrade{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ResolvedOvernightInArrearsCapFloorTrade}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code info} property. + */ + private final MetaProperty info = DirectMetaProperty.ofImmutable( + this, "info", ResolvedOvernightInArrearsCapFloorTrade.class, TradeInfo.class); + /** + * The meta-property for the {@code product} property. + */ + private final MetaProperty product = DirectMetaProperty.ofImmutable( + this, "product", ResolvedOvernightInArrearsCapFloorTrade.class, ResolvedOvernightInArrearsCapFloor.class); + /** + * The meta-property for the {@code premium} property. + */ + private final MetaProperty premium = DirectMetaProperty.ofImmutable( + this, "premium", ResolvedOvernightInArrearsCapFloorTrade.class, Payment.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "info", + "product", + "premium"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade.Builder builder() { + return new ResolvedOvernightInArrearsCapFloorTrade.Builder(); + } + + @Override + public Class beanType() { + return ResolvedOvernightInArrearsCapFloorTrade.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code info} property. + * @return the meta-property, not null + */ + public MetaProperty info() { + return info; + } + + /** + * The meta-property for the {@code product} property. + * @return the meta-property, not null + */ + public MetaProperty product() { + return product; + } + + /** + * The meta-property for the {@code premium} property. + * @return the meta-property, not null + */ + public MetaProperty premium() { + return premium; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 3237038: // info + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).getInfo(); + case -309474065: // product + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).getProduct(); + case -318452137: // premium + return ((ResolvedOvernightInArrearsCapFloorTrade) bean).premium; + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ResolvedOvernightInArrearsCapFloorTrade}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private TradeInfo info; + private ResolvedOvernightInArrearsCapFloor product; + private Payment premium; + + /** + * Restricted constructor. + */ + private Builder() { + applyDefaults(this); + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(ResolvedOvernightInArrearsCapFloorTrade beanToCopy) { + this.info = beanToCopy.getInfo(); + this.product = beanToCopy.getProduct(); + this.premium = beanToCopy.premium; + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 3237038: // info + return info; + case -309474065: // product + return product; + case -318452137: // premium + return premium; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 3237038: // info + this.info = (TradeInfo) newValue; + break; + case -309474065: // product + this.product = (ResolvedOvernightInArrearsCapFloor) newValue; + break; + case -318452137: // premium + this.premium = (Payment) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public ResolvedOvernightInArrearsCapFloorTrade build() { + return new ResolvedOvernightInArrearsCapFloorTrade( + info, + product, + premium); + } + + //----------------------------------------------------------------------- + /** + * Sets the additional trade information, defaulted to an empty instance. + *

+ * This allows additional information to be attached to the trade. + * @param info the new value, not null + * @return this, for chaining, not null + */ + public Builder info(TradeInfo info) { + JodaBeanUtils.notNull(info, "info"); + this.info = info; + return this; + } + + /** + * Sets the resolved overnight in arrears cap/floor product. + *

+ * The product captures the contracted financial details of the trade. + * @param product the new value, not null + * @return this, for chaining, not null + */ + public Builder product(ResolvedOvernightInArrearsCapFloor product) { + JodaBeanUtils.notNull(product, "product"); + this.product = product; + return this; + } + + /** + * Sets the optional premium of the product. + *

+ * For most overnight rate in arrears cap/floor products, a premium is paid upfront. This typically occurs instead + * of periodic payments based on fixed or rate index rates over the lifetime of the product. + *

+ * The premium sign must be compatible with the product Pay/Receive flag. + * @param premium the new value + * @return this, for chaining, not null + */ + public Builder premium(Payment premium) { + this.premium = premium; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("ResolvedOvernightInArrearsCapFloorTrade.Builder{"); + buf.append("info").append('=').append(JodaBeanUtils.toString(info)).append(',').append(' '); + buf.append("product").append('=').append(JodaBeanUtils.toString(product)).append(',').append(' '); + buf.append("premium").append('=').append(JodaBeanUtils.toString(premium)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java new file mode 100644 index 0000000000..84f39867f6 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorLegTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_EONIA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.AdjustableDate; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.index.OvernightIndices; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.basics.value.ValueAdjustment; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.basics.value.ValueStep; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.OvernightAccrualMethod; +import com.opengamma.strata.product.swap.OvernightRateCalculation; + +/** + * Test {@link OvernightInArrearsCapFloorLeg}. + */ +public class OvernightInArrearsCapFloorLegTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2012, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .stubConvention(StubConvention.NONE) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + + private static final double[] NOTIONALS = new double[] {1.0e6, 1.2e6, 0.8e6, 1.0e6}; + private static final double[] STRIKES = new double[] {0.03, 0.0275, 0.02, 0.0345}; + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final List FLOOR_STEPS = new ArrayList(); + private static final List NOTIONAL_STEPS = new ArrayList(); + static { + FLOOR_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(STRIKES[1]))); + FLOOR_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(STRIKES[2]))); + FLOOR_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(STRIKES[3]))); + NOTIONAL_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(NOTIONALS[1]))); + NOTIONAL_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(NOTIONALS[2]))); + NOTIONAL_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(NOTIONALS[3]))); + } + private static final ValueSchedule FLOOR = ValueSchedule.of(STRIKES[0], FLOOR_STEPS); + private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONALS[0], NOTIONAL_STEPS); + + @Test + public void test_builder_full() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .currency(GBP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build(); + assertThat(test.getCalculation()).isEqualTo(RATE_CALCULATION); + assertThat(test.getCapSchedule().get()).isEqualTo(CAP); + assertThat(test.getFloorSchedule()).isNotPresent(); + assertThat(test.getCurrency()).isEqualTo(GBP); + assertThat(test.getNotional()).isEqualTo(NOTIONAL); + assertThat(test.getPaymentDateOffset()).isEqualTo(PAYMENT_OFFSET); + assertThat(test.getPaymentSchedule()).isEqualTo(SCHEDULE); + assertThat(test.getPayReceive()).isEqualTo(PAY); + assertThat(test.getStartDate()).isEqualTo(AdjustableDate.of(START, BUSS_ADJ)); + assertThat(test.getEndDate()).isEqualTo(AdjustableDate.of(END, BUSS_ADJ)); + assertThat(test.getIndex()).isEqualTo(EUR_ESTR); + } + + @Test + public void test_builder_min() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + assertThat(test.getCalculation()).isEqualTo(RATE_CALCULATION); + assertThat(test.getCapSchedule()).isNotPresent(); + assertThat(test.getFloorSchedule().get()).isEqualTo(FLOOR); + assertThat(test.getCurrency()).isEqualTo(EUR); + assertThat(test.getNotional()).isEqualTo(NOTIONAL); + assertThat(test.getPaymentDateOffset()).isEqualTo(DaysAdjustment.NONE); + assertThat(test.getPaymentSchedule()).isEqualTo(SCHEDULE); + assertThat(test.getPayReceive()).isEqualTo(RECEIVE); + assertThat(test.getStartDate()).isEqualTo(AdjustableDate.of(START, BUSS_ADJ)); + assertThat(test.getEndDate()).isEqualTo(AdjustableDate.of(END, BUSS_ADJ)); + } + + @Test + public void test_builder_fail() { + // cap and floor present + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build()); + // cap and floor missing + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .notional(NOTIONAL) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build()) + .payReceive(RECEIVE) + .build()); + // stub type + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .currency(GBP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .stubConvention(StubConvention.SHORT_FINAL) + .build()) + .payReceive(PAY) + .build()); + // accrual method not compounded + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightInArrearsCapFloorLeg.builder() + .calculation(OvernightRateCalculation.builder() + .index(EUR_ESTR) + .accrualMethod(OvernightAccrualMethod.AVERAGED_DAILY) + .build()) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build()); + } + + @Test + public void test_resolve_cap() { + OvernightRateCalculation rateCalc = OvernightRateCalculation.of(EUR_EONIA); + OvernightInArrearsCapFloorLeg base = OvernightInArrearsCapFloorLeg.builder() + .calculation(rateCalc) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + LocalDate[] unadjustedDates = new LocalDate[]{ + START, START.plusMonths(3), + START.plusMonths(6), + START.plusMonths(9), + START.plusMonths(12)}; + OvernightInArrearsCapletFloorletPeriod[] periods = new OvernightInArrearsCapletFloorletPeriod[4]; + for (int i = 0; i < 4; ++i) { + LocalDate start = BUSS_ADJ.adjust(unadjustedDates[i], REF_DATA); + LocalDate end = BUSS_ADJ.adjust(unadjustedDates[i + 1], REF_DATA); + double yearFraction = rateCalc.getDayCount().relativeYearFraction(start, end); + periods[i] = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(CAP.getInitialValue()) + .currency(EUR) + .startDate(start) + .endDate(end) + .unadjustedStartDate(unadjustedDates[i]) + .unadjustedEndDate(unadjustedDates[i + 1]) + .paymentDate(PAYMENT_OFFSET.adjust(end, REF_DATA)) + .notional(NOTIONALS[i]) + .overnightRate(OvernightCompoundedRateComputation.of(EUR_EONIA, start, end, REF_DATA)) + .yearFraction(yearFraction) + .build(); + } + ResolvedOvernightInArrearsCapFloorLeg expected = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periods) + .payReceive(RECEIVE) + .build(); + ResolvedOvernightInArrearsCapFloorLeg computed = base.resolve(REF_DATA); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_resolve_floor() { + OvernightInArrearsCapFloorLeg base = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .currency(EUR) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build(); + LocalDate[] unadjustedDates = new LocalDate[] { + START, + START.plusMonths(3), + START.plusMonths(6), + START.plusMonths(9), + START.plusMonths(12)}; + OvernightInArrearsCapletFloorletPeriod[] periods = new OvernightInArrearsCapletFloorletPeriod[4]; + for (int i = 0; i < 4; ++i) { + LocalDate start = BUSS_ADJ.adjust(unadjustedDates[i], REF_DATA); + LocalDate end = BUSS_ADJ.adjust(unadjustedDates[i + 1], REF_DATA); + double yearFraction = RATE_CALCULATION.getDayCount().relativeYearFraction(start, end); + periods[i] = OvernightInArrearsCapletFloorletPeriod.builder() + .floorlet(STRIKES[i]) + .currency(EUR) + .startDate(start) + .endDate(end) + .unadjustedStartDate(unadjustedDates[i]) + .unadjustedEndDate(unadjustedDates[i + 1]) + .paymentDate(PAYMENT_OFFSET.adjust(end, REF_DATA)) + .notional(-NOTIONALS[i]) + .overnightRate(OvernightCompoundedRateComputation.of(EUR_ESTR, start, end, REF_DATA)) + .yearFraction(yearFraction) + .build(); + } + ResolvedOvernightInArrearsCapFloorLeg expected = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(periods) + .payReceive(PAY) + .build(); + ResolvedOvernightInArrearsCapFloorLeg computed = base.resolve(REF_DATA); + assertThat(computed).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloorLeg test1 = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + coverImmutableBean(test1); + OvernightInArrearsCapFloorLeg test2 = OvernightInArrearsCapFloorLeg.builder() + .calculation(OvernightRateCalculation.of(OvernightIndices.GBP_SONIA)) + .capSchedule(CAP) + .notional(ValueSchedule.of(1000)) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(Frequency.P6M) + .businessDayAdjustment(BUSS_ADJ) + .build()) + .payReceive(PAY) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloorLeg test = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(FLOOR) + .notional(NOTIONAL) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java new file mode 100644 index 0000000000..4742548481 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.date.DayCounts.ACT_360; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.basics.index.OvernightIndices.GBP_SONIA; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.product.swap.FixedRateCalculation; +import com.opengamma.strata.product.swap.NotionalSchedule; +import com.opengamma.strata.product.swap.OvernightRateCalculation; +import com.opengamma.strata.product.swap.PaymentSchedule; +import com.opengamma.strata.product.swap.RateCalculationSwapLeg; +import com.opengamma.strata.product.swap.SwapLeg; + +/** + * Test {@link OvernightInArrearsCapFloor}. + */ +public class OvernightInArrearsCapFloorTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2016, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final ValueSchedule NOTIONAL = ValueSchedule.of(1.0e6); + private static final OvernightInArrearsCapFloorLeg CAPFLOOR_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final SwapLeg PAY_LEG = RateCalculationSwapLeg.builder() + .payReceive(PAY) + .accrualSchedule(SCHEDULE) + .calculation( + FixedRateCalculation.of(0.001, ACT_360)) + .paymentSchedule( + PaymentSchedule.builder() + .paymentFrequency(FREQUENCY) + .paymentDateOffset(DaysAdjustment.NONE) + .build()) + .notionalSchedule( + NotionalSchedule.of(EUR, NOTIONAL)) + .build(); + private static final SwapLeg PAY_LEG_XCCY = RateCalculationSwapLeg.builder() + .payReceive(PAY) + .accrualSchedule(SCHEDULE) + .calculation( + OvernightRateCalculation.of(GBP_SONIA)) + .paymentSchedule( + PaymentSchedule.builder() + .paymentFrequency(FREQUENCY) + .paymentDateOffset(DaysAdjustment.NONE) + .build()) + .notionalSchedule( + NotionalSchedule.of(GBP, NOTIONAL)) + .build(); + + @Test + public void test_of_oneLeg() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg()).isNotPresent(); + assertThat(test.isCrossCurrency()).isFalse(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG); + assertThat(test.isCrossCurrency()).isFalse(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs_xccy() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG_XCCY); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG_XCCY); + assertThat(test.isCrossCurrency()).isTrue(); + assertThat(test.allPaymentCurrencies()).containsOnly(GBP, EUR); + assertThat(test.allCurrencies()).containsOnly(GBP, EUR); + assertThat(test.allIndices()).containsOnly(GBP_SONIA, EUR_ESTR); + } + + @Test + public void test_resolve_oneLeg() { + OvernightInArrearsCapFloor base = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + ResolvedOvernightInArrearsCapFloor test = base.resolve(REF_DATA); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG.resolve(REF_DATA)); + assertThat(test.getPayLeg()).isNotPresent(); + } + + @Test + public void test_resolve_twoLegs() { + OvernightInArrearsCapFloor base = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + ResolvedOvernightInArrearsCapFloor test = base.resolve(REF_DATA); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG.resolve(REF_DATA)); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG.resolve(REF_DATA)); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloor test1 = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + coverImmutableBean(test1); + OvernightInArrearsCapFloorLeg capFloor = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + OvernightInArrearsCapFloor test2 = OvernightInArrearsCapFloor.of(capFloor, PAY_LEG); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloor test = OvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java new file mode 100644 index 0000000000..50154a3081 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/OvernightInArrearsCapFloorTradeTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.product.PortfolioItemSummary; +import com.opengamma.strata.product.PortfolioItemType; +import com.opengamma.strata.product.ProductType; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.swap.OvernightRateCalculation; + +/** + * Test {@link OvernightInArrearsCapFloorTrade}. + */ +public class OvernightInArrearsCapFloorTradeTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 3, 17); + private static final LocalDate END = LocalDate.of(2016, 3, 17); + private static final OvernightRateCalculation RATE_CALCULATION = OvernightRateCalculation.of(EUR_ESTR); + private static final Frequency FREQUENCY = Frequency.P3M; + private static final BusinessDayAdjustment BUSS_ADJ = + BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); + private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() + .startDate(START) + .endDate(END) + .frequency(FREQUENCY) + .businessDayAdjustment(BUSS_ADJ) + .build(); + private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, EUTA); + private static final ValueSchedule CAP = ValueSchedule.of(0.0325); + private static final double NOTIONAL_VALUE = 1.0e6; + private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONAL_VALUE); + private static final OvernightInArrearsCapFloorLeg CAP_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .capSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final OvernightInArrearsCapFloorLeg FLOOR_LEG = OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(RECEIVE) + .build(); + private static final OvernightInArrearsCapFloor PRODUCT = OvernightInArrearsCapFloor.of(CAP_LEG); + private static final OvernightInArrearsCapFloor PRODUCT_FLOOR = OvernightInArrearsCapFloor.of(FLOOR_LEG); + private static final AdjustablePayment PREMIUM = + AdjustablePayment.of(CurrencyAmount.of(EUR, NOTIONAL_VALUE), LocalDate.of(2011, 3, 18)); + private static final TradeInfo TRADE_INFO = TradeInfo.builder() + .tradeDate(LocalDate.of(2011, 3, 15)) + .build(); + + //------------------------------------------------------------------------- + @Test + public void test_builder_full() { + OvernightInArrearsCapFloorTrade test = sut(); + assertThat(test.getPremium().get()).isEqualTo(PREMIUM); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getInfo()).isEqualTo(TRADE_INFO); + assertThat(test.withInfo(TRADE_INFO).getInfo()).isEqualTo(TRADE_INFO); + } + + @Test + public void test_builder_min() { + OvernightInArrearsCapFloorTrade test = OvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertThat(test.getPremium()).isNotPresent(); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getInfo()).isEqualTo(TradeInfo.empty()); + } + + //------------------------------------------------------------------------- + @Test + public void test_summarize() { + OvernightInArrearsCapFloorTrade trade = sut(); + PortfolioItemSummary expected = PortfolioItemSummary.builder() + .id(TRADE_INFO.getId().orElse(null)) + .portfolioItemType(PortfolioItemType.TRADE) + .productType(ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR) + .currencies(Currency.EUR) + .description("5Y EUR 1mm Rec Compounded EUR-ESTR Cap 3.25% / Pay Premium : 17Mar11-17Mar16") + .build(); + assertThat(trade.summarize()).isEqualTo(expected); + } + + @Test + public void test_summarize_floor() { + OvernightInArrearsCapFloorTrade trade = OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT_FLOOR) + .build(); + PortfolioItemSummary expected = PortfolioItemSummary.builder() + .id(TRADE_INFO.getId().orElse(null)) + .portfolioItemType(PortfolioItemType.TRADE) + .productType(ProductType.OVERNIGHT_IN_ARREARS_CAP_FLOOR) + .currencies(Currency.EUR) + .description("5Y EUR 1mm Rec Compounded EUR-ESTR Floor 3.25% : 17Mar11-17Mar16") + .build(); + assertThat(trade.summarize()).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void test_resolve() { + OvernightInArrearsCapFloorTrade test = sut(); + ResolvedOvernightInArrearsCapFloorTrade expected = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT.resolve(REF_DATA)) + .premium(PREMIUM.resolve(REF_DATA)) + .build(); + assertThat(test.resolve(REF_DATA)).isEqualTo(expected); + } + + @Test + public void test_resolve_noPremium() { + OvernightInArrearsCapFloorTrade test = OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .build(); + ResolvedOvernightInArrearsCapFloorTrade expected = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT.resolve(REF_DATA)) + .build(); + assertThat(test.resolve(REF_DATA)).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + OvernightInArrearsCapFloorTrade test1 = sut(); + coverImmutableBean(test1); + OvernightInArrearsCapFloor product = OvernightInArrearsCapFloor.of( + OvernightInArrearsCapFloorLeg.builder() + .calculation(RATE_CALCULATION) + .floorSchedule(CAP) + .notional(NOTIONAL) + .paymentDateOffset(PAYMENT_OFFSET) + .paymentSchedule(SCHEDULE) + .payReceive(PAY) + .build()); + OvernightInArrearsCapFloorTrade test2 = OvernightInArrearsCapFloorTrade.builder() + .product(product) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapFloorTrade test = sut(); + assertSerialization(test); + } + + //------------------------------------------------------------------------- + OvernightInArrearsCapFloorTrade sut() { + return OvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java new file mode 100644 index 0000000000..85bf954d3e --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorLegTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.currency.Currency.GBP; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.basics.index.OvernightIndices.GBP_SONIA; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloorLeg}. + */ +public class ResolvedOvernightInArrearsCapFloorLegTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final double STRIKE = 0.0125; + private static final double NOTIONAL = 1.0e6; + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_1 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 6, 17)) + .unadjustedStartDate(LocalDate.of(2011, 3, 17)) + .unadjustedEndDate(LocalDate.of(2011, 6, 17)) + .paymentDate(LocalDate.of(2011, 6, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 3, 17), + LocalDate.of(2011, 6, 17), + REF_DATA)) + .yearFraction(0.2556) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_2 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_3 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2011, 12, 19)) + .unadjustedStartDate(LocalDate.of(2011, 9, 17)) + .unadjustedEndDate(LocalDate.of(2011, 12, 17)) + .paymentDate(LocalDate.of(2011, 12, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 9, 17), + LocalDate.of(2011, 12, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_4 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 12, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .unadjustedStartDate(LocalDate.of(2011, 12, 17)) + .unadjustedEndDate(LocalDate.of(2012, 3, 17)) + .paymentDate(LocalDate.of(2012, 3, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 12, 17), + LocalDate.of(2012, 3, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + + @Test + public void test_builder() { + ResolvedOvernightInArrearsCapFloorLeg test = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + assertThat(test.getCapletFloorletPeriods()).containsExactly(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4); + assertThat(test.getPayReceive()).isEqualTo(RECEIVE); + assertThat(test.getStartDate()).isEqualTo(PERIOD_1.getStartDate()); + assertThat(test.getEndDate()).isEqualTo(PERIOD_4.getEndDate()); + assertThat(test.getFinalPeriod()).isEqualTo(PERIOD_4); + assertThat(test.getCurrency()).isEqualTo(EUR); + assertThat(test.getIndex()).isEqualTo(EUR_ESTR); + } + + @Test + public void test_builder_fail() { + // two currencies + OvernightInArrearsCapletFloorletPeriod periodGbp = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(GBP) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + assertThatIllegalArgumentException() + .isThrownBy(() -> ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, periodGbp) + .payReceive(RECEIVE) + .build()); + // two indices + OvernightInArrearsCapletFloorletPeriod period = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)).overnightRate(OvernightCompoundedRateComputation.of( + GBP_SONIA, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + assertThatIllegalArgumentException() + .isThrownBy(() -> ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, period) + .payReceive(RECEIVE) + .build()); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloorLeg test1 = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + coverImmutableBean(test1); + ResolvedOvernightInArrearsCapFloorLeg test2 = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_2, PERIOD_3) + .payReceive(PAY) + .build(); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloorLeg test = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java new file mode 100644 index 0000000000..07c38ac87f --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static com.opengamma.strata.product.swap.SwapLegType.FIXED; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.product.rate.FixedRateComputation; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; +import com.opengamma.strata.product.swap.RateAccrualPeriod; +import com.opengamma.strata.product.swap.RatePaymentPeriod; +import com.opengamma.strata.product.swap.ResolvedSwapLeg; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloor}. + */ +public class ResolvedOvernightInArrearsCapFloorTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final double STRIKE = 0.0125; + private static final double NOTIONAL = 1.0e6; + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_1 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 6, 17)) + .unadjustedStartDate(LocalDate.of(2011, 3, 17)) + .unadjustedEndDate(LocalDate.of(2011, 6, 17)) + .paymentDate(LocalDate.of(2011, 6, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 3, 17), + LocalDate.of(2011, 6, 17), + REF_DATA)) + .yearFraction(0.2556) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_2 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 6, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .unadjustedStartDate(LocalDate.of(2011, 6, 17)) + .unadjustedEndDate(LocalDate.of(2011, 9, 17)) + .paymentDate(LocalDate.of(2011, 9, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 6, 17), + LocalDate.of(2011, 9, 17), + REF_DATA)) + .yearFraction(0.2611) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_3 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2011, 12, 19)) + .unadjustedStartDate(LocalDate.of(2011, 9, 17)) + .unadjustedEndDate(LocalDate.of(2011, 12, 17)) + .paymentDate(LocalDate.of(2011, 12, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 9, 17), + LocalDate.of(2011, 12, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod PERIOD_4 = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .notional(NOTIONAL) + .currency(EUR) + .startDate(LocalDate.of(2011, 12, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .unadjustedStartDate(LocalDate.of(2011, 12, 17)) + .unadjustedEndDate(LocalDate.of(2012, 3, 17)) + .paymentDate(LocalDate.of(2012, 3, 21)) + .overnightRate(OvernightCompoundedRateComputation.of( + EUR_ESTR, + LocalDate.of(2011, 12, 17), + LocalDate.of(2012, 3, 17), + REF_DATA)) + .yearFraction(0.2528) + .build(); + static final ResolvedOvernightInArrearsCapFloorLeg CAPFLOOR_LEG = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4) + .payReceive(RECEIVE) + .build(); + + private static final double RATE = 0.015; + private static final RatePaymentPeriod PAY_PERIOD_1 = RatePaymentPeriod.builder() + .paymentDate(LocalDate.of(2011, 9, 21)) + .accrualPeriods(RateAccrualPeriod.builder() + .startDate(LocalDate.of(2011, 3, 17)) + .endDate(LocalDate.of(2011, 9, 19)) + .yearFraction(0.517) + .rateComputation(FixedRateComputation.of(RATE)) + .build()) + .dayCount(ACT_365F) + .currency(EUR) + .notional(-NOTIONAL) + .build(); + private static final RatePaymentPeriod PAY_PERIOD_2 = RatePaymentPeriod.builder() + .paymentDate(LocalDate.of(2012, 3, 21)) + .accrualPeriods(RateAccrualPeriod.builder() + .startDate(LocalDate.of(2011, 9, 19)) + .endDate(LocalDate.of(2012, 3, 19)) + .yearFraction(0.505) + .rateComputation(FixedRateComputation.of(RATE)) + .build()) + .dayCount(ACT_365F) + .currency(EUR) + .notional(-NOTIONAL) + .build(); + static final ResolvedSwapLeg PAY_LEG = ResolvedSwapLeg.builder() + .paymentPeriods(PAY_PERIOD_1, PAY_PERIOD_2) + .type(FIXED) + .payReceive(PAY) + .build(); + + //------------------------------------------------------------------------- + @Test + public void test_of_oneLeg() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg()).isNotPresent(); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + @Test + public void test_of_twoLegs() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + assertThat(test.getCapFloorLeg()).isEqualTo(CAPFLOOR_LEG); + assertThat(test.getPayLeg().get()).isEqualTo(PAY_LEG); + assertThat(test.allPaymentCurrencies()).containsOnly(EUR); + assertThat(test.allIndices()).containsOnly(EUR_ESTR); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloor test1 = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG, PAY_LEG); + coverImmutableBean(test1); + ResolvedOvernightInArrearsCapFloorLeg capFloor = ResolvedOvernightInArrearsCapFloorLeg.builder() + .capletFloorletPeriods(PERIOD_1) + .payReceive(PAY) + .build(); + ResolvedOvernightInArrearsCapFloor test2 = ResolvedOvernightInArrearsCapFloor.of(capFloor); + coverBeanEquals(test1, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloor test = ResolvedOvernightInArrearsCapFloor.of(CAPFLOOR_LEG); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java new file mode 100644 index 0000000000..f51940a15b --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/capfloor/ResolvedOvernightInArrearsCapFloorTradeTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.collect.TestHelper.date; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.product.TradeInfo; + +/** + * Test {@link ResolvedOvernightInArrearsCapFloorTrade}. + */ +public class ResolvedOvernightInArrearsCapFloorTradeTest { + + private static final TradeInfo TRADE_INFO = TradeInfo.of(date(2016, 6, 30)); + private static final ResolvedOvernightInArrearsCapFloor PRODUCT = ResolvedOvernightInArrearsCapFloor.of( + ResolvedOvernightInArrearsCapFloorTest.CAPFLOOR_LEG, + ResolvedOvernightInArrearsCapFloorTest.PAY_LEG); + private static final Payment PREMIUM = Payment.of(CurrencyAmount.of(EUR, -0.001 * 1.0e6), date(2016, 7, 2)); + + //------------------------------------------------------------------------- + @Test + public void test_builder() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertThat(test.getInfo()).isEqualTo(TradeInfo.empty()); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getPremium()).isEqualTo(Optional.empty()); + } + + @Test + public void test_builder_full() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + assertThat(test.getInfo()).isEqualTo(TRADE_INFO); + assertThat(test.getProduct()).isEqualTo(PRODUCT); + assertThat(test.getPremium()).isEqualTo(Optional.of(PREMIUM)); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .info(TRADE_INFO) + .product(PRODUCT) + .premium(PREMIUM) + .build(); + coverImmutableBean(test); + ResolvedOvernightInArrearsCapFloorTrade test2 = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + coverBeanEquals(test, test2); + } + + @Test + public void test_serialization() { + ResolvedOvernightInArrearsCapFloorTrade test = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(PRODUCT) + .build(); + assertSerialization(test); + } + +} From 5f86c7a47fc84c7515dc4556e63a103c3a0401ce Mon Sep 17 00:00:00 2001 From: Andras Vig <93195277+Andras1022@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:44:02 +0200 Subject: [PATCH 08/29] Adding Inauguration Day to MXMC (#2678) * PROD-42681: Adding Inauguration Day to MXMC * PROD-42681: Adding Inauguration Day to MXMC --- .../basics/date/GlobalHolidayCalendars.java | 7 ++++++- .../date/GlobalHolidayCalendarsTest.java | 2 ++ .../basics/date/GlobalHolidayCalendars.bin | Bin 0 -> 178050 bytes 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin diff --git a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java index 2ddc39b5a5..e3394ab0f9 100644 --- a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java +++ b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java @@ -1169,6 +1169,7 @@ private static void addHungarianSaturdays(List holidays, Set holidays = new ArrayList<>(2000); for (int year = 1950; year <= 2099; year++) { @@ -1186,6 +1187,10 @@ static ImmutableHolidayCalendar generateMexicoCity() { holidays.add(date(year, 5, 1)); // independence holidays.add(date(year, 9, 16)); + // inaguration day - occurring once in every 6 years (2024, 2030, etc). + if (year >= 2024 && (year + 4) % 6 == 0) { + holidays.add(date(year, 10, 1)); + } // dead holidays.add(date(year, 11, 2)); // revolution @@ -1196,7 +1201,7 @@ static ImmutableHolidayCalendar generateMexicoCity() { holidays.add(date(year, 12, 25)); } removeSatSun(holidays); - return ImmutableHolidayCalendar.of(HolidayCalendarId.of("MXMC"), holidays, SATURDAY, SUNDAY); + return ImmutableHolidayCalendar.of(HolidayCalendarIds.MXMC, holidays, SATURDAY, SUNDAY); } // generate BRBD diff --git a/modules/basics/src/test/java/com/opengamma/strata/basics/date/GlobalHolidayCalendarsTest.java b/modules/basics/src/test/java/com/opengamma/strata/basics/date/GlobalHolidayCalendarsTest.java index 29d36327ec..53c854a798 100644 --- a/modules/basics/src/test/java/com/opengamma/strata/basics/date/GlobalHolidayCalendarsTest.java +++ b/modules/basics/src/test/java/com/opengamma/strata/basics/date/GlobalHolidayCalendarsTest.java @@ -944,6 +944,8 @@ public static Object[][] data_mxmc() { md(5, 1), md(9, 16), md(11, 2), md(11, 16), md(12, 12), md(12, 25))}, {2016, mds(2016, md(1, 1), md(2, 1), md(3, 21), md(3, 24), md(3, 25), md(5, 1), md(9, 16), md(11, 2), md(11, 21), md(12, 12), md(12, 25))}, + {2024, mds(2024, md(1, 1), md(2, 5), md(3, 18), md(3, 28), md(3, 29), + md(5, 1), md(9, 16), md(10, 1), md(11, 2), md(11, 18), md(12, 12), md(12, 25))}, }; } diff --git a/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin b/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin new file mode 100644 index 0000000000000000000000000000000000000000..5599a791a5c8ee428b52049b0ad8b59064fbcd2c GIT binary patch literal 178050 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 literal 0 HcmV?d00001 From c9ff50049f202bb3f9490b3eb8d5c93ab5b3af52 Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Wed, 16 Oct 2024 14:45:06 +0000 Subject: [PATCH 09/29] [maven-release-plugin] prepare release v2.12.42 --- examples/pom.xml | 6 +++++- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 59152ac8ad..d2d703540c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.42-SNAPSHOT + 2.12.42 jar Strata-Examples Example code to demonstrate use of Strata @@ -272,4 +272,8 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> + + + v2.12.42 + diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index c46914670c..641d76cc6a 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 32a5769e52..247f69371c 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 3bb57365d7..3a77bd924a 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index bcb8272ab2..c33c7e669d 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 9f26593859..d17c16deea 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index ef3910df18..0a4912f546 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index a98acd197f..e3fb97ce4f 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 069169a041..93f575933f 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index f71ddad74f..bebe1fc86b 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.42 diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index f9e746e558..f6c289be58 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 43d4967edd..be44821292 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index 5f9129e618..aad926ec02 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42-SNAPSHOT + 2.12.42 .. strata-report diff --git a/pom.xml b/pom.xml index 924db250d2..ce6ef3f2d1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.42-SNAPSHOT + 2.12.42 pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.42 From 6cf8752379972cd1a00d82c54dfbc0f79c4e58d1 Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Wed, 16 Oct 2024 14:45:08 +0000 Subject: [PATCH 10/29] [maven-release-plugin] prepare for next development iteration --- examples/pom.xml | 6 +----- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index d2d703540c..8f504e9229 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.42 + 2.12.43-SNAPSHOT jar Strata-Examples Example code to demonstrate use of Strata @@ -272,8 +272,4 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> - - - v2.12.42 - diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 641d76cc6a..da026b9501 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 247f69371c..af02a59f04 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 3a77bd924a..fee1aab278 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index c33c7e669d..493539c4e7 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index d17c16deea..60b62bbfb8 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 0a4912f546..a2fc6b5103 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index e3fb97ce4f..50d0d221a7 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 93f575933f..32348ee22b 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index bebe1fc86b..5a2cd0de67 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.42 + HEAD diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index f6c289be58..9402f178cc 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index be44821292..3598b4c230 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index aad926ec02..dd1356d3ee 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.42 + 2.12.43-SNAPSHOT .. strata-report diff --git a/pom.xml b/pom.xml index ce6ef3f2d1..6ced91402f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.42 + 2.12.43-SNAPSHOT pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.42 + HEAD From 1f44ccea19b02f3263f2eedf494b9b0cf3cf88ce Mon Sep 17 00:00:00 2001 From: Alexis Skitini Date: Wed, 16 Oct 2024 22:05:23 +0300 Subject: [PATCH 11/29] Overnight CapFloor CSV plugin (#2679) * Overnight CapFloor CSV plugin * copyright * variable names --------- Co-authored-by: Alexis Skitini --- .../csv/IborCapFloorTradeCsvPlugin.java | 4 +- ...rnightInArrearsCapFloorTradeCsvPlugin.java | 330 ++++++++++++++++++ .../loader/csv/TradeCsvInfoResolver.java | 36 +- .../config/base/TradeCsvParserPlugin.ini | 1 + .../config/base/TradeCsvWriterPlugin.ini | 1 + .../strata/loader/csv/TradeCsvLoaderTest.java | 63 +++- .../opengamma/strata/loader/csv/trades.csv | 3 + 7 files changed, 432 insertions(+), 6 deletions(-) create mode 100644 modules/loader/src/main/java/com/opengamma/strata/loader/csv/OvernightInArrearsCapFloorTradeCsvPlugin.java diff --git a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/IborCapFloorTradeCsvPlugin.java b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/IborCapFloorTradeCsvPlugin.java index 4f6094dc71..e69bbc6fbb 100644 --- a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/IborCapFloorTradeCsvPlugin.java +++ b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/IborCapFloorTradeCsvPlugin.java @@ -68,7 +68,7 @@ import com.opengamma.strata.product.swap.IborRateCalculation; /** - * Handles the CSV file format for CapFloor trades. + * Handles the CSV file format for IBOR cap/floor trades. */ public class IborCapFloorTradeCsvPlugin implements TradeCsvParserPlugin, TradeCsvWriterPlugin { @@ -122,7 +122,7 @@ public Optional parseTrade( TradeCsvInfoResolver resolver) { if (requiredJavaType.isAssignableFrom(IborCapFloorTrade.class)) { - return Optional.of(resolver.parseCapFloorTrade(baseRow, info)); + return Optional.of(resolver.parseIborCapFloorTrade(baseRow, info)); } return Optional.empty(); } diff --git a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/OvernightInArrearsCapFloorTradeCsvPlugin.java b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/OvernightInArrearsCapFloorTradeCsvPlugin.java new file mode 100644 index 0000000000..513d0f1174 --- /dev/null +++ b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/OvernightInArrearsCapFloorTradeCsvPlugin.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.loader.csv; + +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.CAP_FLOOR_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.CURRENCY_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DATE_ADJ_CAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DATE_ADJ_CNV_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.DIRECTION_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.END_DATE_CAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.END_DATE_CNV_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.END_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.FIRST_REGULAR_START_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.INDEX_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.LAST_REGULAR_END_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.NOTIONAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.OVERRIDE_START_DATE_CAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.OVERRIDE_START_DATE_CNV_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.OVERRIDE_START_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PAYMENT_FREQUENCY_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_AMOUNT_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_CURRENCY_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_DATE_CAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_DATE_CNV_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.PREMIUM_DIRECTION_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.ROLL_CONVENTION_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.START_DATE_CAL_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.START_DATE_CNV_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.START_DATE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.STRIKE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.STUB_CONVENTION_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderColumns.TRADE_TYPE_FIELD; +import static com.opengamma.strata.loader.csv.CsvLoaderUtils.formattedPercentage; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.opengamma.strata.basics.currency.AdjustablePayment; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.date.AdjustableDate; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.index.Index; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.collect.io.CsvOutput; +import com.opengamma.strata.collect.io.CsvRow; +import com.opengamma.strata.collect.result.ParseFailureException; +import com.opengamma.strata.loader.LoaderUtils; +import com.opengamma.strata.product.Trade; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloor; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorLeg; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorTrade; +import com.opengamma.strata.product.common.CapFloor; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.swap.OvernightRateCalculation; + +/** + * Handles the CSV file format for Overnight in arrears cap/floor trades. + */ +public class OvernightInArrearsCapFloorTradeCsvPlugin + implements TradeCsvParserPlugin, TradeCsvWriterPlugin { + + /** + * The singleton instance of the plugin. + */ + public static final OvernightInArrearsCapFloorTradeCsvPlugin INSTANCE = + new OvernightInArrearsCapFloorTradeCsvPlugin(); + + private static final ImmutableSet HEADERS = ImmutableSet.of( + CAP_FLOOR_FIELD, + START_DATE_FIELD, + END_DATE_FIELD, + PAYMENT_FREQUENCY_FIELD, + DIRECTION_FIELD, + CURRENCY_FIELD, + STRIKE_FIELD, + NOTIONAL_FIELD, + INDEX_FIELD, + PREMIUM_AMOUNT_FIELD, + PREMIUM_CURRENCY_FIELD, + PREMIUM_DIRECTION_FIELD, + PREMIUM_DATE_FIELD, + PREMIUM_DATE_CNV_FIELD, + PREMIUM_DATE_CAL_FIELD, + DATE_ADJ_CNV_FIELD, + DATE_ADJ_CAL_FIELD, + START_DATE_CNV_FIELD, + START_DATE_CAL_FIELD, + END_DATE_CNV_FIELD, + END_DATE_CAL_FIELD, + STUB_CONVENTION_FIELD, + ROLL_CONVENTION_FIELD, + FIRST_REGULAR_START_DATE_FIELD, + LAST_REGULAR_END_DATE_FIELD, + OVERRIDE_START_DATE_FIELD, + OVERRIDE_START_DATE_CNV_FIELD, + OVERRIDE_START_DATE_CAL_FIELD); + + //------------------------------------------------------------------------- + @Override + public Set tradeTypeNames() { + return ImmutableSet.of("OVERNIGHTCAPFLOOR", "OVERNIGHT CAPFLOOR"); + } + + @Override + public Optional parseTrade( + Class requiredJavaType, + CsvRow baseRow, + List additionalRows, + TradeInfo info, + TradeCsvInfoResolver resolver) { + + if (requiredJavaType.isAssignableFrom(OvernightInArrearsCapFloorTrade.class)) { + return Optional.of(resolver.parseOvernightCapFloorTrade(baseRow, info)); + } + return Optional.empty(); + } + + @Override + public String getName() { + return OvernightInArrearsCapFloorTrade.class.getSimpleName(); + } + + @Override + public Set> supportedTradeTypes() { + return ImmutableSet.of(OvernightInArrearsCapFloorTrade.class); + } + + //------------------------------------------------------------------------- + /** + * Parses from the CSV row. + * + * @param row the CSV row + * @param info the trade info + * @param resolver the resolver used to parse additional information + * @return the parsed trade + */ + static OvernightInArrearsCapFloorTrade parseCapFloor(CsvRow row, TradeInfo info, TradeCsvInfoResolver resolver) { + OvernightInArrearsCapFloorTrade overnightCapFloorTrade = parseRow(row, info); + return resolver.completeTrade(row, overnightCapFloorTrade); + } + + //------------------------------------------------------------------------- + // parses the row to a trade + private static OvernightInArrearsCapFloorTrade parseRow(CsvRow row, TradeInfo info) { + OvernightInArrearsCapFloor overnightInArrearsCapFloor = parseOvernightInArrearsCapFloor(row); + Optional paymentOpt = parsePremium(row); + return paymentOpt.map(adjustablePayment -> + OvernightInArrearsCapFloorTrade.builder() + .info(info) + .product(overnightInArrearsCapFloor) + .premium(adjustablePayment) + .build()) + .orElseGet(() -> OvernightInArrearsCapFloorTrade.builder() + .info(info) + .product(overnightInArrearsCapFloor) + .build()); + } + + // parse an OvernightInArrearsCapFloor + private static OvernightInArrearsCapFloor parseOvernightInArrearsCapFloor(CsvRow row) { + PayReceive payReceive = row.getValue(DIRECTION_FIELD, LoaderUtils::parsePayReceive); + Currency currency = row.getValue(CURRENCY_FIELD, LoaderUtils::parseCurrency); + ValueSchedule strike = ValueSchedule.of(row.getValue(STRIKE_FIELD, LoaderUtils::parseDoublePercent)); + double notional = row.getValue(NOTIONAL_FIELD, LoaderUtils::parseDouble); + OvernightIndex overnightIndex = parseOvernightIndex(row); + PeriodicSchedule paymentSchedule = parseSchedule(row, currency); + OvernightInArrearsCapFloorLeg.Builder capFloorLegBuilder = OvernightInArrearsCapFloorLeg.builder() + .payReceive(payReceive) + .paymentSchedule(paymentSchedule) + .currency(currency) + .notional(ValueSchedule.of(notional)) + .calculation(OvernightRateCalculation.of(overnightIndex)); + CapFloor capFloorType = LoaderUtils.parseCapFloor(row.getValue(CAP_FLOOR_FIELD)); + if (capFloorType.isCap()) { + capFloorLegBuilder.capSchedule(strike); + } else { + capFloorLegBuilder.floorSchedule(strike); + } + OvernightInArrearsCapFloorLeg capFloorLeg = capFloorLegBuilder.build(); + return OvernightInArrearsCapFloor.of(capFloorLeg); + } + + // parse the overnight index + private static OvernightIndex parseOvernightIndex(CsvRow row) { + Index index = row.getValue(INDEX_FIELD, LoaderUtils::findIndex); + if (index instanceof OvernightIndex) { + return (OvernightIndex) index; + } else { + throw new ParseFailureException("Index '{value}' is not an overnight index", index.getName()); + } + } + + // parse the periodic schedule for CapFloor + private static PeriodicSchedule parseSchedule(CsvRow row, Currency currency) { + PeriodicSchedule.Builder builder = PeriodicSchedule.builder(); + // basics + builder.startDate(row.getValue(START_DATE_FIELD, LoaderUtils::parseDate)); + builder.endDate(row.getValue(END_DATE_FIELD, LoaderUtils::parseDate)); + builder.frequency(Frequency.parse(row.getValue(PAYMENT_FREQUENCY_FIELD))); + // stub period is not allowed for CapFloor trade + builder.stubConvention(StubConvention.NONE); + // adjustments + BusinessDayAdjustment dateAdj = + CsvLoaderUtils.parseBusinessDayAdjustment(row, DATE_ADJ_CNV_FIELD, DATE_ADJ_CAL_FIELD) + .orElse(BusinessDayAdjustment.NONE); + builder.businessDayAdjustment(dateAdj); + CsvLoaderUtils.parseBusinessDayAdjustment(row, START_DATE_CNV_FIELD, START_DATE_CAL_FIELD) + .ifPresent(builder::startDateBusinessDayAdjustment); + CsvLoaderUtils.parseBusinessDayAdjustment(row, END_DATE_CNV_FIELD, END_DATE_CAL_FIELD) + .ifPresent(builder::endDateBusinessDayAdjustment); + // optionals + row.findValue(ROLL_CONVENTION_FIELD) + .map(LoaderUtils::parseRollConvention) + .ifPresent(builder::rollConvention); + row.findValue(FIRST_REGULAR_START_DATE_FIELD) + .map(LoaderUtils::parseDate) + .ifPresent(builder::firstRegularStartDate); + row.findValue(LAST_REGULAR_END_DATE_FIELD) + .map(LoaderUtils::parseDate) + .ifPresent(builder::lastRegularEndDate); + Optional overrideDateOpt = row.findValue(OVERRIDE_START_DATE_FIELD) + .map(ignored -> CsvLoaderUtils.parseAdjustableDate( + row, + OVERRIDE_START_DATE_FIELD, + OVERRIDE_START_DATE_CNV_FIELD, + OVERRIDE_START_DATE_CAL_FIELD, + BusinessDayConventions.MODIFIED_FOLLOWING, + currency)); + overrideDateOpt.ifPresent(builder::overrideStartDate); + return builder.build(); + } + + // parse the upfront premium amount + private static Optional parsePremium(CsvRow row) { + Optional premiumAmountOpt = row.findValue(PREMIUM_AMOUNT_FIELD) + .map(LoaderUtils::parseDouble); + if (premiumAmountOpt.isPresent()) { + Currency premiumCurrency = row.getValue(PREMIUM_CURRENCY_FIELD, LoaderUtils::parseCurrency); + PayReceive premiumDirection = row.getValue(PREMIUM_DIRECTION_FIELD, LoaderUtils::parsePayReceive); + LocalDate premiumDate = row.getValue(PREMIUM_DATE_FIELD, LoaderUtils::parseDate); + CurrencyAmount premiumAmount = CurrencyAmount.of(premiumCurrency, premiumAmountOpt.get()); + AdjustablePayment payment = premiumDirection.isPay() ? + AdjustablePayment.ofPay(premiumAmount, premiumDate) : + AdjustablePayment.ofReceive(premiumAmount, premiumDate); + return Optional.of(payment); + } + return Optional.empty(); + } + + //------------------------------------------------------------------------- + @Override + public Set headers(List trades) { + return HEADERS; + } + + @Override + public void writeCsv(CsvOutput.CsvRowOutputWithHeaders csv, OvernightInArrearsCapFloorTrade trade) { + OvernightInArrearsCapFloorLeg capFloorLeg = trade.getProduct().getCapFloorLeg(); + csv.writeCell(TRADE_TYPE_FIELD, "OvernightCapFloor"); + String capFloorType; + double strike; + if (capFloorLeg.getCapSchedule().isPresent()) { + capFloorType = "Cap"; + strike = capFloorLeg.getCapSchedule().get().getInitialValue(); + } else if (capFloorLeg.getFloorSchedule().isPresent()) { + capFloorType = "Floor"; + strike = capFloorLeg.getFloorSchedule().get().getInitialValue(); + } else { + throw new UnsupportedOperationException("Unknown CapFloor type, trade is missing cap or floor schedule"); + } + csv.writeCell(CAP_FLOOR_FIELD, capFloorType); + writePeriodicSchedule(csv, capFloorLeg.getPaymentSchedule()); + csv.writeCell(DIRECTION_FIELD, capFloorLeg.getPayReceive()); + csv.writeCell(CURRENCY_FIELD, capFloorLeg.getCurrency()); + csv.writeCell(STRIKE_FIELD, formattedPercentage(strike)); + csv.writeCell(NOTIONAL_FIELD, capFloorLeg.getNotional().getInitialValue()); + csv.writeCell(INDEX_FIELD, capFloorLeg.getCalculation().getIndex()); + trade.getPremium().ifPresent(premium -> CsvWriterUtils.writePremiumFields(csv, premium)); + csv.writeNewLine(); + } + + private void writePeriodicSchedule(CsvOutput.CsvRowOutputWithHeaders csv, PeriodicSchedule paymentSchedule) { + csv.writeCell(START_DATE_FIELD, paymentSchedule.getStartDate()); + csv.writeCell(END_DATE_FIELD, paymentSchedule.getEndDate()); + csv.writeCell(PAYMENT_FREQUENCY_FIELD, paymentSchedule.getFrequency()); + csv.writeCell(DATE_ADJ_CNV_FIELD, paymentSchedule.getBusinessDayAdjustment().getConvention()); + csv.writeCell(DATE_ADJ_CAL_FIELD, paymentSchedule.getBusinessDayAdjustment().getCalendar()); + paymentSchedule.getStartDateBusinessDayAdjustment().ifPresent(startDateAdjustment -> { + csv.writeCell(START_DATE_CNV_FIELD, startDateAdjustment.getConvention()); + csv.writeCell(START_DATE_CAL_FIELD, startDateAdjustment.getCalendar()); + }); + paymentSchedule.getEndDateBusinessDayAdjustment().ifPresent(endDateAdjustment -> { + csv.writeCell(END_DATE_CNV_FIELD, endDateAdjustment.getConvention()); + csv.writeCell(END_DATE_CAL_FIELD, endDateAdjustment.getCalendar()); + }); + paymentSchedule.getStubConvention().ifPresent( + stubConvention -> csv.writeCell(STUB_CONVENTION_FIELD, stubConvention)); + paymentSchedule.getRollConvention().ifPresent( + rollConvention -> csv.writeCell(ROLL_CONVENTION_FIELD, rollConvention)); + paymentSchedule.getFirstRegularStartDate().ifPresent( + firstRegDate -> csv.writeCell(FIRST_REGULAR_START_DATE_FIELD, firstRegDate)); + paymentSchedule.getLastRegularEndDate().ifPresent( + lastRegDate -> csv.writeCell(LAST_REGULAR_END_DATE_FIELD, lastRegDate)); + paymentSchedule.getOverrideStartDate().ifPresent(overrideStartDate -> { + csv.writeCell(OVERRIDE_START_DATE_FIELD, overrideStartDate.getUnadjusted()); + csv.writeCell(OVERRIDE_START_DATE_CNV_FIELD, overrideStartDate.getAdjustment().getConvention()); + csv.writeCell(OVERRIDE_START_DATE_CAL_FIELD, overrideStartDate.getAdjustment().getCalendar()); + }); + } + + //------------------------------------------------------------------------- + // Restricted constructor. + private OvernightInArrearsCapFloorTradeCsvPlugin() { + } + +} diff --git a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/TradeCsvInfoResolver.java b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/TradeCsvInfoResolver.java index 00501f8e64..d72840b211 100644 --- a/modules/loader/src/main/java/com/opengamma/strata/loader/csv/TradeCsvInfoResolver.java +++ b/modules/loader/src/main/java/com/opengamma/strata/loader/csv/TradeCsvInfoResolver.java @@ -21,6 +21,7 @@ import com.opengamma.strata.product.TradeInfo; import com.opengamma.strata.product.TradeInfoBuilder; import com.opengamma.strata.product.capfloor.IborCapFloorTrade; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorTrade; import com.opengamma.strata.product.common.CcpId; import com.opengamma.strata.product.credit.CdsIndexTrade; import com.opengamma.strata.product.credit.CdsTrade; @@ -338,6 +339,23 @@ public default IborCapFloorTrade completeTrade(CsvRow row, IborCapFloorTrade tra return completeTradeCommon(row, trade); } + /** + * Completes the CapFloor trade, potentially parsing additional columns. + *

+ * This is called after the trade has been parsed and after + * {@link #parseTradeInfo(CsvRow, TradeInfoBuilder)}. + *

+ * By default this calls {@link #completeTradeCommon(CsvRow, Trade)}. + * + * @param row the CSV row to parse + * @param trade the parsed trade + * @return the updated trade + */ + public default OvernightInArrearsCapFloorTrade completeTrade(CsvRow row, OvernightInArrearsCapFloorTrade trade) { + //do nothing + return completeTradeCommon(row, trade); + } + //------------------------------------------------------------------------- /** * Parses a FRA trade from CSV. @@ -486,17 +504,29 @@ public default CdsIndexTrade parseCdsIndexTrade(CsvRow row, TradeInfo info) { } /** - * Parses a CapFloor trade from CSV. + * Parses an IBOR CapFloor trade from CSV. * * @param row the CSV row to parse * @param info the trade info - * @return the CapFloor trade + * @return the IBOR CapFloor trade * @throws RuntimeException if the row contains invalid data */ - public default IborCapFloorTrade parseCapFloorTrade(CsvRow row, TradeInfo info) { + public default IborCapFloorTrade parseIborCapFloorTrade(CsvRow row, TradeInfo info) { return IborCapFloorTradeCsvPlugin.parseCapFloor(row, info, this); } + /** + * Parses an overnight CapFloor trade from CSV. + * + * @param row the CSV row to parse + * @param info the trade info + * @return the overnight CapFloor trade + * @throws RuntimeException if the row contains invalid data + */ + public default OvernightInArrearsCapFloorTrade parseOvernightCapFloorTrade(CsvRow row, TradeInfo info) { + return OvernightInArrearsCapFloorTradeCsvPlugin.parseCapFloor(row, info, this); + } + //------------------------------------------------------------------------- /** * Parses any kind of trade from CSV before standard matching. diff --git a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini index 57c69cc204..3c9a0fea98 100644 --- a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini +++ b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvParserPlugin.ini @@ -17,6 +17,7 @@ com.opengamma.strata.loader.csv.FxSingleTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxSwapTradeCsvPlugin = constants com.opengamma.strata.loader.csv.FxVanillaOptionTradeCsvPlugin = constants com.opengamma.strata.loader.csv.IborCapFloorTradeCsvPlugin = constants +com.opengamma.strata.loader.csv.OvernightInArrearsCapFloorTradeCsvPlugin = constants com.opengamma.strata.loader.csv.SecurityTradeCsvPlugin = constants com.opengamma.strata.loader.csv.SwapTradeCsvPlugin = constants com.opengamma.strata.loader.csv.SwaptionTradeCsvPlugin = constants diff --git a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini index e14bc33b53..8a840721c0 100644 --- a/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini +++ b/modules/loader/src/main/resources/META-INF/com/opengamma/strata/config/base/TradeCsvWriterPlugin.ini @@ -21,6 +21,7 @@ com.opengamma.strata.loader.csv.CdsTradeCsvPlugin = constants com.opengamma.strata.loader.csv.CdsIndexTradeCsvPlugin = constants com.opengamma.strata.loader.csv.SecurityTradeCsvPlugin = constants com.opengamma.strata.loader.csv.IborCapFloorTradeCsvPlugin = constants +com.opengamma.strata.loader.csv.OvernightInArrearsCapFloorTradeCsvPlugin = constants com.opengamma.strata.loader.csv.GenericSecurityTradeCsvPlugin = constants # The set of alternate names diff --git a/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java b/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java index 0ae3077bde..fed06aad02 100644 --- a/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java +++ b/modules/loader/src/test/java/com/opengamma/strata/loader/csv/TradeCsvLoaderTest.java @@ -96,6 +96,9 @@ import com.opengamma.strata.product.capfloor.IborCapFloor; import com.opengamma.strata.product.capfloor.IborCapFloorLeg; import com.opengamma.strata.product.capfloor.IborCapFloorTrade; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloor; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorLeg; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorTrade; import com.opengamma.strata.product.common.CcpIds; import com.opengamma.strata.product.common.LongShort; import com.opengamma.strata.product.credit.Cds; @@ -2097,7 +2100,7 @@ public void test_load_filtered() { ImmutableList.of(FILE.getCharSource()), ImmutableList.of(FraTrade.class, TermDepositTrade.class)); assertThat(trades.getValue()).hasSize(6); - assertThat(trades.getFailures()).hasSize(24); + assertThat(trades.getFailures()).hasSize(26); assertThat(trades.getFailures().get(0).getMessage()).isEqualTo( "Trade type not allowed " + SwapTrade.class.getName() + ", only these types are supported: FraTrade, TermDepositTrade"); } @@ -2322,6 +2325,64 @@ public void test_load_CapFloor() { checkRoundtrip(IborCapFloorTrade.class, filtered, expected0, expected1); } + @Test + public void test_load_OvernightCapFloor() { + TradeCsvLoader test = TradeCsvLoader.standard(); + ValueWithFailures> trades = test.load(FILE); + + List filtered = trades.getValue().stream() + .flatMap(filtering(OvernightInArrearsCapFloorTrade.class)) + .collect(toImmutableList()); + assertThat(filtered).hasSize(2); + + OvernightInArrearsCapFloorTrade expected0 = OvernightInArrearsCapFloorTrade.builder() + .info(TradeInfo.builder() + .id(StandardId.of("OG", "123456")) + .tradeDate(date(2017, 6, 1)) + .build()) + .product(OvernightInArrearsCapFloor.of(OvernightInArrearsCapFloorLeg.builder() + .payReceive(RECEIVE) + .paymentSchedule(PeriodicSchedule.of( + date(2020, 3, 10), + date(2025, 3, 10), + Frequency.P3M, + BusinessDayAdjustment.NONE, + StubConvention.NONE, + false)) + .currency(USD) + .notional(ValueSchedule.of(10_000_000)) + .calculation(OvernightRateCalculation.of(OvernightIndices.USD_SOFR)) + .capSchedule(ValueSchedule.of(0.021)) + .build())) + .build(); + assertBeanEquals(expected0, filtered.get(0)); + + OvernightInArrearsCapFloorTrade expected1 = OvernightInArrearsCapFloorTrade.builder() + .info(TradeInfo.builder() + .id(StandardId.of("OG", "123457")) + .tradeDate(date(2017, 6, 1)) + .build()) + .product(OvernightInArrearsCapFloor.of(OvernightInArrearsCapFloorLeg.builder() + .payReceive(PAY) + .paymentSchedule(PeriodicSchedule.of( + date(2020, 3, 10), + date(2030, 3, 10), + Frequency.P6M, + BusinessDayAdjustment.NONE, + StubConvention.NONE, + false)) + .currency(EUR) + .notional(ValueSchedule.of(15_000_000)) + .calculation(OvernightRateCalculation.of(OvernightIndices.EUR_ESTR)) + .floorSchedule(ValueSchedule.of(0.005)) + .build())) + .premium(AdjustablePayment.ofReceive(CurrencyAmount.of(EUR, 5000), date(2020, 3, 10))) + .build(); + assertBeanEquals(expected1, filtered.get(1)); + + checkRoundtrip(OvernightInArrearsCapFloorTrade.class, filtered, expected0, expected1); + } + //------------------------------------------------------------------------- @Test public void test_load_invalidNoHeader() { diff --git a/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/trades.csv b/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/trades.csv index 8bb1368405..5348549445 100644 --- a/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/trades.csv +++ b/modules/loader/src/test/resources/com/opengamma/strata/loader/csv/trades.csv @@ -45,3 +45,6 @@ CDS Index,OG,123455,01/06/2017,,,,,Buy,,,,,5,,,GBP,1000000,01/06/2017,01/06/2022 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, CapFloor,OG,123452,01/06/2017,,,,,,,,USD-LIBOR-3M,,,,,USD,10000000,10/03/2020,10/03/2025,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Cap,Receive,2.1,3M,,,, CapFloor,OG,123453,01/06/2017,,,,,,,,EUR-EURIBOR-6M,,,,,EUR,15000000,10/03/2020,10/03/2030,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Receive,EUR,5000,10/03/2020,,,,,,,,,,,,,,Floor,Pay,0.5,6M,,,, +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +OvernightCapFloor,OG,123456,01/06/2017,,,,,,,,USD-SOFR,,,,,USD,10000000,10/03/2020,10/03/2025,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Cap,Receive,2.1,3M,,,, +OvernightCapFloor,OG,123457,01/06/2017,,,,,,,,EUR-ESTR,,,,,EUR,15000000,10/03/2020,10/03/2030,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Receive,EUR,5000,10/03/2020,,,,,,,,,,,,,,Floor,Pay,0.5,6M,,,, From d49427193d0df7b735caf680f0f22a0d8a0750f3 Mon Sep 17 00:00:00 2001 From: Yuki Iwashita Date: Wed, 16 Oct 2024 21:10:21 +0100 Subject: [PATCH 12/29] Overnight rate in arrears cap floor pricers (#2680) * overnight cap product * tests * javadoc * line * clean * white spaces * pricers * failing test for accrual * pricers * test * spaces * addressed review comments * error message --- ...tInArrearsCapletFloorletPeriodAmounts.java | 327 +++++++++++++++++ ...rsCapletFloorletPeriodCurrencyAmounts.java | 328 +++++++++++++++++ ...brOvernightInArrearsCapFloorLegPricer.java | 222 ++++++++++++ ...ernightInArrearsCapFloorProductPricer.java | 257 ++++++++++++++ ...OvernightInArrearsCapFloorTradePricer.java | 248 +++++++++++++ ...htInArrearsCapletFloorletPeriodPricer.java | 66 +++- ...rrearsCapletFloorletPeriodAmountsTest.java | 110 ++++++ ...pletFloorletPeriodCurrencyAmountsTest.java | 111 ++++++ ...ernightInArrearsCapFloorLegPricerTest.java | 335 ++++++++++++++++++ ...ghtInArrearsCapFloorProductPricerTest.java | 150 ++++++++ ...abrOvernightInArrearsCapFloorTestData.java | 169 +++++++++ ...nightInArrearsCapFloorTradePricerTest.java | 186 ++++++++++ ...ArrearsCapletFloorletPeriodPricerTest.java | 100 +++++- 13 files changed, 2598 insertions(+), 11 deletions(-) create mode 100644 modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmounts.java create mode 100644 modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.java create mode 100644 modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricer.java create mode 100644 modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricer.java create mode 100644 modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricer.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmountsTest.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmountsTest.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricerTest.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricerTest.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTestData.java create mode 100644 modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricerTest.java diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmounts.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmounts.java new file mode 100644 index 0000000000..86bc1a6723 --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmounts.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; + +/** + * A map of double values keyed by overnight rate in arrears caplet/floorlet periods. + *

+ * The double values should represent the same metric. + */ +@BeanDefinition +public final class OvernightInArrearsCapletFloorletPeriodAmounts + implements ImmutableBean, Serializable { + + /** + * The map of overnight rate in arrears caplet/floorlet periods to the double amount. + */ + @PropertyDefinition(validate = "notNull") + private final ImmutableMap amounts; + + //------------------------------------------------------------------------- + /** + * Obtains an instance of double amounts. + * + * @param doubleMap the map of doubles + * @return the instance + */ + public static OvernightInArrearsCapletFloorletPeriodAmounts of( + Map doubleMap) { + + return new OvernightInArrearsCapletFloorletPeriodAmounts(doubleMap); + } + + /** + * Gets a double amount for the provided overnight rate in arrears caplet/floorlet. + * + * @param period the caplet/floorlet + * @return the double amount, empty if missing + */ + public Optional findAmount(OvernightInArrearsCapletFloorletPeriod period) { + return Optional.ofNullable(amounts.get(period)); + } + + /** + * Gets a double amount for the provided overnight rate in arrears caplet/floorlet. + * + * @param period the caplet/floorlet + * @return the double amount + * @throws IllegalArgumentException if the period is missing + */ + public double getAmount(OvernightInArrearsCapletFloorletPeriod period) { + return findAmount(period).orElseThrow( + () -> new IllegalArgumentException("Could not find double amount for " + period)); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapletFloorletPeriodAmounts}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapletFloorletPeriodAmounts.Meta meta() { + return OvernightInArrearsCapletFloorletPeriodAmounts.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapletFloorletPeriodAmounts.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapletFloorletPeriodAmounts.Builder builder() { + return new OvernightInArrearsCapletFloorletPeriodAmounts.Builder(); + } + + private OvernightInArrearsCapletFloorletPeriodAmounts( + Map amounts) { + JodaBeanUtils.notNull(amounts, "amounts"); + this.amounts = ImmutableMap.copyOf(amounts); + } + + @Override + public OvernightInArrearsCapletFloorletPeriodAmounts.Meta metaBean() { + return OvernightInArrearsCapletFloorletPeriodAmounts.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the map of overnight rate in arrears caplet/floorlet periods to the double amount. + * @return the value of the property, not null + */ + public ImmutableMap getAmounts() { + return amounts; + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapletFloorletPeriodAmounts other = (OvernightInArrearsCapletFloorletPeriodAmounts) obj; + return JodaBeanUtils.equal(amounts, other.amounts); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(amounts); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("OvernightInArrearsCapletFloorletPeriodAmounts{"); + buf.append("amounts").append('=').append(JodaBeanUtils.toString(amounts)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapletFloorletPeriodAmounts}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code amounts} property. + */ + @SuppressWarnings({"unchecked", "rawtypes" }) + private final MetaProperty> amounts = DirectMetaProperty.ofImmutable( + this, "amounts", OvernightInArrearsCapletFloorletPeriodAmounts.class, (Class) ImmutableMap.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "amounts"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return amounts; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapletFloorletPeriodAmounts.Builder builder() { + return new OvernightInArrearsCapletFloorletPeriodAmounts.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapletFloorletPeriodAmounts.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code amounts} property. + * @return the meta-property, not null + */ + public MetaProperty> amounts() { + return amounts; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return ((OvernightInArrearsCapletFloorletPeriodAmounts) bean).getAmounts(); + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapletFloorletPeriodAmounts}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private Map amounts = ImmutableMap.of(); + + /** + * Restricted constructor. + */ + private Builder() { + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapletFloorletPeriodAmounts beanToCopy) { + this.amounts = beanToCopy.getAmounts(); + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return amounts; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @SuppressWarnings("unchecked") + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + this.amounts = (Map) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapletFloorletPeriodAmounts build() { + return new OvernightInArrearsCapletFloorletPeriodAmounts( + amounts); + } + + //----------------------------------------------------------------------- + /** + * Sets the map of overnight rate in arrears caplet/floorlet periods to the double amount. + * @param amounts the new value, not null + * @return this, for chaining, not null + */ + public Builder amounts(Map amounts) { + JodaBeanUtils.notNull(amounts, "amounts"); + this.amounts = amounts; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("OvernightInArrearsCapletFloorletPeriodAmounts.Builder{"); + buf.append("amounts").append('=').append(JodaBeanUtils.toString(amounts)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.java new file mode 100644 index 0000000000..c7ea8d6df0 --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import java.io.Serializable; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; + +/** + * A map of currency amounts keyed by overnight in arrears caplet/floorlet periods. + *

+ * The currency amounts should represent the same metric. + */ +@BeanDefinition +public final class OvernightInArrearsCapletFloorletPeriodCurrencyAmounts + implements ImmutableBean, Serializable { + + /** + * The map of overnight rate in arrears caplet/floorlet periods to the currency amount. + */ + @PropertyDefinition(validate = "notNull") + private final ImmutableMap amounts; + + //------------------------------------------------------------------------- + /** + * Obtains an instance of currency amounts. + * + * @param currencyAmountMap the map of currency amounts + * @return the instance + */ + public static OvernightInArrearsCapletFloorletPeriodCurrencyAmounts of( + Map currencyAmountMap) { + + return new OvernightInArrearsCapletFloorletPeriodCurrencyAmounts(currencyAmountMap); + } + + /** + * Gets a currency amount for the provided overnight rate in arrears caplet/floorlet. + * + * @param period the caplet/floorlet + * @return the currency amount, empty if missing + */ + public Optional findAmount(OvernightInArrearsCapletFloorletPeriod period) { + return Optional.ofNullable(amounts.get(period)); + } + + /** + * Gets a currency amount for the provided overnight rate in arrears caplet/floorlet. + * + * @param period the caplet/floorlet + * @return the currency amount + * @throws IllegalArgumentException if the period is missing + */ + public CurrencyAmount getAmount(OvernightInArrearsCapletFloorletPeriod period) { + return findAmount(period).orElseThrow( + () -> new IllegalArgumentException("Could not find currency amount for " + period)); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapletFloorletPeriodCurrencyAmounts}. + * @return the meta-bean, not null + */ + public static OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Meta meta() { + return OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Meta.INSTANCE; + } + + static { + MetaBean.register(OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Builder builder() { + return new OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Builder(); + } + + private OvernightInArrearsCapletFloorletPeriodCurrencyAmounts( + Map amounts) { + JodaBeanUtils.notNull(amounts, "amounts"); + this.amounts = ImmutableMap.copyOf(amounts); + } + + @Override + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Meta metaBean() { + return OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the map of overnight rate in arrears caplet/floorlet periods to the currency amount. + * @return the value of the property, not null + */ + public ImmutableMap getAmounts() { + return amounts; + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts other = (OvernightInArrearsCapletFloorletPeriodCurrencyAmounts) obj; + return JodaBeanUtils.equal(amounts, other.amounts); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(amounts); + return hash; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("OvernightInArrearsCapletFloorletPeriodCurrencyAmounts{"); + buf.append("amounts").append('=').append(JodaBeanUtils.toString(amounts)); + buf.append('}'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code OvernightInArrearsCapletFloorletPeriodCurrencyAmounts}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code amounts} property. + */ + @SuppressWarnings({"unchecked", "rawtypes" }) + private final MetaProperty> amounts = DirectMetaProperty.ofImmutable( + this, "amounts", OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.class, (Class) ImmutableMap.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "amounts"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return amounts; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Builder builder() { + return new OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Builder(); + } + + @Override + public Class beanType() { + return OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code amounts} property. + * @return the meta-property, not null + */ + public MetaProperty> amounts() { + return amounts; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return ((OvernightInArrearsCapletFloorletPeriodCurrencyAmounts) bean).getAmounts(); + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code OvernightInArrearsCapletFloorletPeriodCurrencyAmounts}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private Map amounts = ImmutableMap.of(); + + /** + * Restricted constructor. + */ + private Builder() { + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(OvernightInArrearsCapletFloorletPeriodCurrencyAmounts beanToCopy) { + this.amounts = beanToCopy.getAmounts(); + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + return amounts; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @SuppressWarnings("unchecked") + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case -879772901: // amounts + this.amounts = (Map) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts build() { + return new OvernightInArrearsCapletFloorletPeriodCurrencyAmounts( + amounts); + } + + //----------------------------------------------------------------------- + /** + * Sets the map of overnight rate in arrears caplet/floorlet periods to the currency amount. + * @param amounts the new value, not null + * @return this, for chaining, not null + */ + public Builder amounts(Map amounts) { + JodaBeanUtils.notNull(amounts, "amounts"); + this.amounts = amounts; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.Builder{"); + buf.append("amounts").append('=').append(JodaBeanUtils.toString(amounts)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricer.java new file mode 100644 index 0000000000..9feb394b4d --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricer.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import java.time.ZoneOffset; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.collect.MapStream; +import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; +import com.opengamma.strata.pricer.rate.RatesProvider; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloorLeg; + +/** + * Pricer for overnight rate in arrears cap/floor legs in SABR model. + */ +public class SabrOvernightInArrearsCapFloorLegPricer { + + /** + * Default implementation. + */ + public static final SabrOvernightInArrearsCapFloorLegPricer DEFAULT = + new SabrOvernightInArrearsCapFloorLegPricer(SabrOvernightInArrearsCapletFloorletPeriodPricer.DEFAULT); + + /** + * The period pricer. + */ + private final SabrOvernightInArrearsCapletFloorletPeriodPricer periodPricer; + + /** + * Creates an instance. + * + * @param periodPricer the pricer for {@link OvernightInArrearsCapletFloorletPeriod}. + */ + public SabrOvernightInArrearsCapFloorLegPricer(SabrOvernightInArrearsCapletFloorletPeriodPricer periodPricer) { + this.periodPricer = ArgChecker.notNull(periodPricer, "periodPricer"); + } + + //------------------------------------------------------------------------- + /** + * Obtains the underlying period pricer. + * + * @return the period pricer + */ + public SabrOvernightInArrearsCapletFloorletPeriodPricer getPeriodPricer() { + return periodPricer; + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value of the overnight rate in arrears cap/floor leg. + *

+ * The present value of the leg is the value on the valuation date. + * The result is returned using the payment currency of the leg. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value + */ + public CurrencyAmount presentValue( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + return capFloorLeg.getCapletFloorletPeriods().stream() + .map(period -> periodPricer.presentValue(period, ratesProvider, volatilities)) + .reduce(CurrencyAmount::plus) + .orElse(CurrencyAmount.zero(capFloorLeg.getCurrency())); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value for each caplet/floorlet of the overnight rate in arrears cap/floor leg. + *

+ * The present value of each caplet/floorlet is the value on the valuation date. + * The result is returned using the payment currency of the leg. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present values + */ + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts presentValueCapletFloorletPeriods( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + Map periodPresentValues = + MapStream.of(capFloorLeg.getCapletFloorletPeriods()) + .mapValues(period -> periodPricer.presentValue(period, ratesProvider, volatilities)) + .toMap(); + return OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(periodPresentValues); + } + + //------------------------------------------------------------------------- + /** + * Calculates the current cash of the overnight in arrears cap/floor leg. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the current cash + */ + public CurrencyAmount currentCash( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + return capFloorLeg.getCapletFloorletPeriods().stream() + .filter(period -> period.getPaymentDate().equals(ratesProvider.getValuationDate())) + .map(period -> periodPricer.presentValue(period, ratesProvider, volatilities)) + .reduce(CurrencyAmount::plus) + .orElse(CurrencyAmount.zero(capFloorLeg.getCurrency())); + } + + //------------------------------------------------------------------------- + /** + * Calculates the forward rates for each caplet/floorlet of the overnight rate in arrears cap/floor leg. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @return the forward rates + */ + public OvernightInArrearsCapletFloorletPeriodAmounts forwardRates( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider) { + + Map forwardRates = + MapStream.of(capFloorLeg.getCapletFloorletPeriods()) + .filterKeys(period -> !ratesProvider.getValuationDate().isAfter(period.getOvernightRate().getEndDate())) + .mapValues(period -> periodPricer.forwardRate(period, ratesProvider)) + .toMap(); + return OvernightInArrearsCapletFloorletPeriodAmounts.of(forwardRates); + } + + //------------------------------------------------------------------------- + /** + * Calculates the implied volatilities for each caplet/floorlet of the overnight rate in arrears cap/floor leg. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the implied volatilities + */ + public OvernightInArrearsCapletFloorletPeriodAmounts impliedVolatilities( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + ImmutableMap impliedVolatilities = + MapStream.of(capFloorLeg.getCapletFloorletPeriods()) + .filterKeys(period -> volatilities.relativeTime( + period.getOvernightRate().getEndDate().atStartOfDay(ZoneOffset.UTC)) >= 0) + .mapValues(period -> periodPricer.impliedVolatility(period, ratesProvider, volatilities)) + .toMap(); + return OvernightInArrearsCapletFloorletPeriodAmounts.of(impliedVolatilities); + } + + /** + * Calculates the present value sensitivity of the overnight in arrears cap/floor leg to the rate curves. + *

+ * The present value sensitivity is computed in a "sticky model parameter" style, i.e. the sensitivity to the + * curve nodes with the SABR model parameters unchanged. This sensitivity does not include a potential + * re-calibration of the model parameters to the raw market data. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the point sensitivity to the rate curves + */ + public PointSensitivityBuilder presentValueSensitivityRatesStickyModel( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + return capFloorLeg.getCapletFloorletPeriods().stream() + .map(period -> periodPricer.presentValueSensitivityRatesStickyModel(period, ratesProvider, volatilities)) + .reduce(PointSensitivityBuilder::combinedWith) + .orElse(PointSensitivityBuilder.none()); + } + + /** + * Calculates the present value sensitivity to the SABR model parameters of the overnight in arrears cap/floor. + *

+ * The sensitivity of the present value to the SABR model parameters, alpha, beta, rho and nu. + * + * @param capFloorLeg the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the point sensitivity to the SABR model parameters + */ + public PointSensitivityBuilder presentValueSensitivityModelParamsSabr( + ResolvedOvernightInArrearsCapFloorLeg capFloorLeg, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + validate(ratesProvider, volatilities); + return capFloorLeg.getCapletFloorletPeriods().stream() + .map(period -> periodPricer.presentValueSensitivityModelParamsSabr(period, ratesProvider, volatilities)) + .reduce(PointSensitivityBuilder::combinedWith) + .orElse(PointSensitivityBuilder.none()); + } + + //------------------------------------------------------------------------- + private void validate(RatesProvider ratesProvider, IborCapletFloorletVolatilities volatilities) { + ArgChecker.isTrue(volatilities.getValuationDate().equals(ratesProvider.getValuationDate()), + "volatility and rate data must be for the same date"); + } + +} diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricer.java new file mode 100644 index 0000000000..b271c26ba7 --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricer.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.MultiCurrencyAmount; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; +import com.opengamma.strata.pricer.rate.RatesProvider; +import com.opengamma.strata.pricer.swap.DiscountingSwapLegPricer; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorLeg; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloor; +import com.opengamma.strata.product.swap.SwapLeg; + +/** + * Pricer for overnight rate in arrears cap/floor products in SABR model. + */ +public class SabrOvernightInArrearsCapFloorProductPricer { + + /** + * Default implementation. + */ + public static final SabrOvernightInArrearsCapFloorProductPricer DEFAULT = + new SabrOvernightInArrearsCapFloorProductPricer( + SabrOvernightInArrearsCapFloorLegPricer.DEFAULT, + DiscountingSwapLegPricer.DEFAULT); + /** + * The pricer for {@link OvernightInArrearsCapFloorLeg} + */ + private final SabrOvernightInArrearsCapFloorLegPricer capFloorLegPricer; + /** + * The pricer for {@link SwapLeg}. + */ + private final DiscountingSwapLegPricer payLegPricer; + + /** + * Creates an instance. + * + * @param capFloorLegPricer the pricer for {@link OvernightInArrearsCapFloorLeg} + * @param payLegPricer the pricer for {@link SwapLeg} + */ + public SabrOvernightInArrearsCapFloorProductPricer( + SabrOvernightInArrearsCapFloorLegPricer capFloorLegPricer, + DiscountingSwapLegPricer payLegPricer) { + + this.capFloorLegPricer = ArgChecker.notNull(capFloorLegPricer, "capFloorLegPricer"); + this.payLegPricer = ArgChecker.notNull(payLegPricer, "payLegPricer"); + } + + //------------------------------------------------------------------------- + /** + * Gets the cap/floor leg pricer. + * + * @return the cap/floor leg pricer + */ + public SabrOvernightInArrearsCapFloorLegPricer getCapFloorLegPricer() { + return capFloorLegPricer; + } + + /** + * Gets the pay leg pricer. + * + * @return the pay leg pricer + */ + public DiscountingSwapLegPricer getPayLegPricer() { + return payLegPricer; + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value of the overnight rate in arrears cap/floor product. + *

+ * The present value of the product is the value on the valuation date. + *

+ * The cap/floor leg and pay leg are typically in the same currency, + * thus the present value is expressed as a single currency amount in most cases. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value + */ + public MultiCurrencyAmount presentValue( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + CurrencyAmount pvCapFloorLeg = capFloorLegPricer.presentValue( + capFloor.getCapFloorLeg(), + ratesProvider, + volatilities); + if (!capFloor.getPayLeg().isPresent()) { + return MultiCurrencyAmount.of(pvCapFloorLeg); + } + CurrencyAmount pvPayLeg = payLegPricer.presentValue(capFloor.getPayLeg().get(), ratesProvider); + return MultiCurrencyAmount.of(pvCapFloorLeg).plus(pvPayLeg); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value for each caplet/floorlet of the overnight rate in arrears cap/floor product. + *

+ * The present value of each caplet/floorlet is the value on the valuation date. + * The result is returned using the payment currency of the leg. + *

+ * The present value will not be calculated for the pay leg if the product has one. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present values + */ + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts presentValueCapletFloorletPeriods( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return capFloorLegPricer.presentValueCapletFloorletPeriods(capFloor.getCapFloorLeg(), ratesProvider, volatilities); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value rates sensitivity of the overnight rate in arrears cap/floor product. + *

+ * The present value sensitivity is computed in a "sticky model parameter" style, i.e., the sensitivity to the + * curve nodes with the SABR model parameters unchanged. + * This sensitivity does not include a potential re-calibration of the model parameters to the raw market data. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value sensitivity + */ + public PointSensitivityBuilder presentValueSensitivityRatesStickyModel( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + PointSensitivityBuilder pvSensiCapFloorLeg = capFloorLegPricer.presentValueSensitivityRatesStickyModel( + capFloor.getCapFloorLeg(), + ratesProvider, + volatilities); + if (!capFloor.getPayLeg().isPresent()) { + return pvSensiCapFloorLeg; + } + PointSensitivityBuilder pvSensiPayLeg = payLegPricer.presentValueSensitivity(capFloor.getPayLeg().get(), ratesProvider); + return pvSensiCapFloorLeg.combinedWith(pvSensiPayLeg); + } + + //------------------------------------------------------------------------- + /** + * Calculates the currency exposure of the overnight rate in arrears cap/floor product. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the currency exposure + */ + public MultiCurrencyAmount currencyExposure( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + CurrencyAmount ceCapFloorLeg = capFloorLegPricer.presentValue( + capFloor.getCapFloorLeg(), + ratesProvider, + volatilities); + if (!capFloor.getPayLeg().isPresent()) { + return MultiCurrencyAmount.of(ceCapFloorLeg); + } + MultiCurrencyAmount cePayLeg = payLegPricer.currencyExposure(capFloor.getPayLeg().get(), ratesProvider); + return cePayLeg.plus(ceCapFloorLeg); + } + + //------------------------------------------------------------------------- + /** + * Calculates the current cash of the overnight rate in arrears cap/floor product. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the current cash + */ + public MultiCurrencyAmount currentCash( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + CurrencyAmount ccCapFloorLeg = capFloorLegPricer.currentCash( + capFloor.getCapFloorLeg(), + ratesProvider, + volatilities); + if (!capFloor.getPayLeg().isPresent()) { + return MultiCurrencyAmount.of(ccCapFloorLeg); + } + CurrencyAmount ccPayLeg = payLegPricer.currentCash(capFloor.getPayLeg().get(), ratesProvider); + return MultiCurrencyAmount.of(ccPayLeg).plus(ccCapFloorLeg); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value volatility sensitivity of the overnight rata in arrears cap/floor product. + *

+ * The sensitivity of the present value to the SABR model parameters, alpha, beta, rho and nu. + * + * @param capFloor the cap/floor product + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value sensitivity + */ + public PointSensitivityBuilder presentValueSensitivityModelParamsSabr( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return capFloorLegPricer.presentValueSensitivityModelParamsSabr( + capFloor.getCapFloorLeg(), + ratesProvider, + volatilities); + } + + //------------------------------------------------------------------------- + /** + * Calculates the forward rates for each caplet/floorlet of the overnight rate in arrears cap/floor. + * + * @param capFloor the cap/floor + * @param ratesProvider the rates provider + * @return the forward rates + */ + public OvernightInArrearsCapletFloorletPeriodAmounts forwardRates( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider) { + + return capFloorLegPricer.forwardRates(capFloor.getCapFloorLeg(), ratesProvider); + } + + //------------------------------------------------------------------------- + /** + * Calculates the implied volatilities for each caplet/floorlet of the overnight rate in arrears cap/floor. + * + * @param capFloor the cap/floor + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the implied volatilities + */ + public OvernightInArrearsCapletFloorletPeriodAmounts impliedVolatilities( + ResolvedOvernightInArrearsCapFloor capFloor, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return capFloorLegPricer.impliedVolatilities(capFloor.getCapFloorLeg(), ratesProvider, volatilities); + } + +} diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricer.java new file mode 100644 index 0000000000..130bdbf996 --- /dev/null +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricer.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.MultiCurrencyAmount; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.market.sensitivity.PointSensitivities; +import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; +import com.opengamma.strata.pricer.DiscountingPaymentPricer; +import com.opengamma.strata.pricer.rate.RatesProvider; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloor; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloorTrade; + +/** + * Pricer for overnight rate in arrears cap/floor trades in SABR model. + */ +public class SabrOvernightInArrearsCapFloorTradePricer { + + /** + * Default implementation. + */ + public static final SabrOvernightInArrearsCapFloorTradePricer DEFAULT = + new SabrOvernightInArrearsCapFloorTradePricer( + SabrOvernightInArrearsCapFloorProductPricer.DEFAULT, + DiscountingPaymentPricer.DEFAULT); + /** + * The pricer for {@link ResolvedOvernightInArrearsCapFloor}. + */ + private final SabrOvernightInArrearsCapFloorProductPricer productPricer; + /** + * Pricer for {@link Payment}. + */ + private final DiscountingPaymentPricer paymentPricer; + + /** + * Creates an instance. + * + * @param productPricer the pricer for {@link ResolvedOvernightInArrearsCapFloor} + * @param paymentPricer the pricer for {@link Payment} + */ + public SabrOvernightInArrearsCapFloorTradePricer( + SabrOvernightInArrearsCapFloorProductPricer productPricer, + DiscountingPaymentPricer paymentPricer) { + + this.productPricer = ArgChecker.notNull(productPricer, "productPricer"); + this.paymentPricer = ArgChecker.notNull(paymentPricer, "paymentPricer"); + } + + //------------------------------------------------------------------------- + /** + * Gets the product pricer. + * + * @return the product pricer + */ + public SabrOvernightInArrearsCapFloorProductPricer getProductPricer() { + return productPricer; + } + + /** + * Gets the payment pricer. + * + * @return the payment pricer + */ + public DiscountingPaymentPricer getPaymentPricer() { + return paymentPricer; + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value of the overnight rate in arrears cap/floor trade. + *

+ * The present value of the trade is the value on the valuation date. + *

+ * The cap/floor leg and pay leg are typically in the same currency, + * thus the present value is expressed as a single currency amount in most cases. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value + */ + public MultiCurrencyAmount presentValue( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + MultiCurrencyAmount pvProduct = productPricer.presentValue(trade.getProduct(), ratesProvider, volatilities); + if (!trade.getPremium().isPresent()) { + return pvProduct; + } + CurrencyAmount pvPremium = paymentPricer.presentValue(trade.getPremium().get(), ratesProvider); + return pvProduct.plus(pvPremium); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value for each caplet/floorlet of the overnight rate in arrears cap/floor trade. + *

+ * The present value of each caplet/floorlet is the value on the valuation date. + * The result is returned using the payment currency of the leg. + *

+ * The present value will not be calculated for the trade premium or for the pay leg + * if the cap/floor product has one. + * + * @param trade the cap/floor leg + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present values + */ + public OvernightInArrearsCapletFloorletPeriodCurrencyAmounts presentValueCapletFloorletPeriods( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return productPricer.presentValueCapletFloorletPeriods(trade.getProduct(), ratesProvider, volatilities); + } + + //------------------------------------------------------------------------- + /** + * Calculates the present value rates sensitivity of the overnight rate in arrears cap/floor trade. + *

+ * The present value sensitivity is computed in a "sticky model parameter" style, i.e. the sensitivity to the + * curve nodes with the SABR model parameters unchanged. This sensitivity does not include a potential + * re-calibration of the model parameters to the raw market data. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value sensitivity + */ + public PointSensitivities presentValueSensitivityRatesStickyModel( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + PointSensitivityBuilder pvSensiProduct = + productPricer.presentValueSensitivityRatesStickyModel(trade.getProduct(), ratesProvider, volatilities); + if (!trade.getPremium().isPresent()) { + return pvSensiProduct.build(); + } + PointSensitivityBuilder pvSensiPremium = + getPaymentPricer().presentValueSensitivity(trade.getPremium().get(), ratesProvider); + return pvSensiProduct.combinedWith(pvSensiPremium).build(); + } + + /** + * Calculates the present value volatility sensitivity of the overnight rate in arrears cap/floor trade. + *

+ * The sensitivity of the present value to the SABR model parameters, alpha, beta, rho and nu. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the present value sensitivity + */ + public PointSensitivityBuilder presentValueSensitivityModelParamsSabr( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return productPricer.presentValueSensitivityModelParamsSabr(trade.getProduct(), ratesProvider, volatilities); + } + + //------------------------------------------------------------------------- + /** + * Calculates the currency exposure of the overnight rate in arrears cap/floor trade. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the currency exposure + */ + public MultiCurrencyAmount currencyExposure( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + MultiCurrencyAmount ceProduct = productPricer.currencyExposure(trade.getProduct(), ratesProvider, volatilities); + if (!trade.getPremium().isPresent()) { + return ceProduct; + } + CurrencyAmount pvPremium = paymentPricer.presentValue(trade.getPremium().get(), ratesProvider); + return ceProduct.plus(pvPremium); + } + + //------------------------------------------------------------------------- + /** + * Calculates the current cash of the overnight rate in arrears cap/floor trade. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the current cash + */ + public MultiCurrencyAmount currentCash( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + MultiCurrencyAmount ccProduct = productPricer.currentCash(trade.getProduct(), ratesProvider, volatilities); + if (!trade.getPremium().isPresent()) { + return ccProduct; + } + Payment premium = trade.getPremium().get(); + if (premium.getDate().equals(ratesProvider.getValuationDate())) { + ccProduct = ccProduct.plus(premium.getCurrency(), premium.getAmount()); + } + return ccProduct; + } + + //------------------------------------------------------------------------- + /** + * Calculates the forward rates for each caplet/floorlet of the overnight rate in arrears cap/floor trade. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @return the forward rates + */ + public OvernightInArrearsCapletFloorletPeriodAmounts forwardRates( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider) { + + return productPricer.forwardRates(trade.getProduct(), ratesProvider); + } + + //------------------------------------------------------------------------- + /** + * Calculates the implied volatilities for each caplet/floorlet of the overnight rate in arrears cap/floor trade. + * + * @param trade the cap/floor trade + * @param ratesProvider the rates provider + * @param volatilities the volatilities + * @return the implied volatilities + */ + public OvernightInArrearsCapletFloorletPeriodAmounts impliedVolatilities( + ResolvedOvernightInArrearsCapFloorTrade trade, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities volatilities) { + + return productPricer.impliedVolatilities(trade.getProduct(), ratesProvider, volatilities); + } + +} diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricer.java index 9741c53092..156eb4f005 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricer.java @@ -17,6 +17,7 @@ import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.value.ValueDerivatives; +import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.collect.array.DoubleArray; import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; import com.opengamma.strata.pricer.ZeroRateSensitivity; @@ -74,9 +75,9 @@ private SabrOvernightInArrearsCapletFloorletPeriodPricer(SabrInArrearsVolatility /** * Computes the present value in the SABR model with effective parameters. * - * @param period the caplet/floorlet period - * @param ratesProvider the rates provider - * @param sabrVolatilities the SABR volatilities + * @param period the caplet/floorlet period + * @param ratesProvider the rates provider + * @param sabrVolatilities the SABR volatilities * @return the present value */ public CurrencyAmount presentValue( @@ -120,9 +121,9 @@ public CurrencyAmount presentValue( /** * Computes the present value sensitivity to the rate with "sticky SABR model parameters". * - * @param period the caplet/floorlet period - * @param ratesProvider the rates provider - * @param sabrVolatilities the SABR volatilities + * @param period the caplet/floorlet period + * @param ratesProvider the rates provider + * @param sabrVolatilities the SABR volatilities * @return the present value rate sensitivity */ public PointSensitivityBuilder presentValueSensitivityRatesStickyModel( @@ -182,9 +183,9 @@ public PointSensitivityBuilder presentValueSensitivityRatesStickyModel( /** * Computes the present value sensitivity to the SABR model parameters. * - * @param period the caplet/floorlet period - * @param ratesProvider the rates provider - * @param sabrVolatilities the SABR volatilities + * @param period the caplet/floorlet period + * @param ratesProvider the rates provider + * @param sabrVolatilities the SABR volatilities * @return the present value model parameters sensitivity */ public PointSensitivityBuilder presentValueSensitivityModelParamsSabr( @@ -239,4 +240,51 @@ public PointSensitivityBuilder presentValueSensitivityModelParamsSabr( IborCapletFloorletSabrSensitivity.of(name, startTime, NU, currency, paramHat.get(3))); } + //------------------------------------------------------------------------- + /** + * Computes the implied volatility of the caplet/floorlet. + * + * @param period the caplet/floorlet period + * @param ratesProvider the rates provider + * @param sabrVolatilities the SABR volatilities + * @return the implied volatility + */ + public double impliedVolatility( + OvernightInArrearsCapletFloorletPeriod period, + RatesProvider ratesProvider, + SabrParametersIborCapletFloorletVolatilities sabrVolatilities) { + + OvernightCompoundedRateComputation onComputation = period.getOvernightRate(); + LocalDate startDate = onComputation.getStartDate(); + LocalDate endDate = onComputation.getEndDate(); + double startTime = sabrVolatilities.relativeTime(startDate.atStartOfDay(ZoneOffset.UTC)); // ON rates don't have an exact fixing time + double endTime = sabrVolatilities.relativeTime(endDate.atStartOfDay(ZoneOffset.UTC)); + ArgChecker.isTrue(endTime >= 0d, "Option must be before expiry to compute an implied volatility"); + double strike = period.getStrike(); + double forward = ON_FUNCT.rate(onComputation, onComputation.getStartDate(), onComputation.getEndDate(), ratesProvider); + double alpha = sabrVolatilities.alpha(startTime); // parameters at start of composition period, for coherence with term rate caplets + double beta = sabrVolatilities.beta(startTime); + double rho = sabrVolatilities.rho(startTime); + double nu = sabrVolatilities.nu(startTime); + double shift = sabrVolatilities.shift(startTime); + SabrFormulaData sabr = SabrFormulaData.of(alpha, beta, rho, nu); + SabrFormulaData sabrAdjusted = sabrInArrearsFunction.effectiveSabr(sabr, startTime, endTime); + return sabrVolatilities.getParameters().getSabrVolatilityFormula() + .volatility(forward + shift, strike + shift, endTime, + sabrAdjusted.getAlpha(), sabrAdjusted.getBeta(), sabrAdjusted.getRho(), sabrAdjusted.getNu()); + } + + //------------------------------------------------------------------------- + /** + * Computes the forward rate for the caplet/floorlet. + * + * @param period the caplet/floorlet period + * @param ratesProvider the rates provider + * @return the forward rate + */ + public double forwardRate(OvernightInArrearsCapletFloorletPeriod period, RatesProvider ratesProvider) { + OvernightCompoundedRateComputation onComputation = period.getOvernightRate(); + return ON_FUNCT.rate(onComputation, onComputation.getStartDate(), onComputation.getEndDate(), ratesProvider); + } + } diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmountsTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmountsTest.java new file mode 100644 index 0000000000..31f8962f27 --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodAmountsTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; + +/** + * Test {@link OvernightInArrearsCapletFloorletPeriodAmounts}. + */ +public class OvernightInArrearsCapletFloorletPeriodAmountsTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 1, 5); + private static final LocalDate END = LocalDate.of(2011, 7, 5); + private static final double NOTIONAL = 1_000_000; + private static final double STRIKE = 0.01; + private static final OvernightCompoundedRateComputation RATE_COMP = OvernightCompoundedRateComputation.of( + EUR_ESTR, + START, + END, + REF_DATA); + private static final OvernightInArrearsCapletFloorletPeriod CAPLET_LONG = + OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .startDate(START) + .endDate(END) + .yearFraction(0.5) + .notional(NOTIONAL) + .overnightRate(RATE_COMP) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod CAPLET_SHORT = + OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .startDate(START) + .endDate(END) + .yearFraction(0.5) + .notional(NOTIONAL * -1) + .overnightRate(RATE_COMP) + .build(); + private static final Map CAPLET_DOUBLE_MAP = ImmutableMap.of( + CAPLET_LONG, + 1d); + + @Test + void test_of() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertThat(test.getAmounts()).isEqualTo(CAPLET_DOUBLE_MAP); + } + + @Test + void test_findAmount() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertThat(test.findAmount(CAPLET_LONG)).contains(1d); + } + + @Test + void test_findAmountEmpty() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertThat(test.findAmount(CAPLET_SHORT)).isEmpty(); + } + + @Test + void test_getAmount() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertThat(test.getAmount(CAPLET_LONG)).isEqualTo(1d); + } + + @Test + void test_getAmountThrows() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertThatIllegalArgumentException().isThrownBy(() -> test.getAmount(CAPLET_SHORT)) + .withMessage("Could not find double amount for " + CAPLET_SHORT); + } + + @Test + void coverage() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + coverImmutableBean(test); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapletFloorletPeriodAmounts test = + OvernightInArrearsCapletFloorletPeriodAmounts.of(CAPLET_DOUBLE_MAP); + assertSerialization(test); + } +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmountsTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmountsTest.java new file mode 100644 index 0000000000..8ff5d3e480 --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/OvernightInArrearsCapletFloorletPeriodCurrencyAmountsTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Currency; +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; +import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation; + +/** + * Test {@link OvernightInArrearsCapletFloorletPeriodCurrencyAmounts}. + */ +public class OvernightInArrearsCapletFloorletPeriodCurrencyAmountsTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final LocalDate START = LocalDate.of(2011, 1, 5); + private static final LocalDate END = LocalDate.of(2011, 7, 5); + private static final double NOTIONAL = 1_000_000; + private static final double STRIKE = 0.01; + private static final OvernightCompoundedRateComputation RATE_COMP = OvernightCompoundedRateComputation.of( + EUR_ESTR, + START, + END, + REF_DATA); + private static final OvernightInArrearsCapletFloorletPeriod CAPLET_LONG = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .startDate(START) + .endDate(END) + .yearFraction(0.5) + .notional(NOTIONAL) + .overnightRate(RATE_COMP) + .build(); + private static final OvernightInArrearsCapletFloorletPeriod CAPLET_SHORT = OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(STRIKE) + .startDate(START) + .endDate(END) + .yearFraction(0.5) + .notional(NOTIONAL * -1) + .overnightRate(RATE_COMP) + .build(); + private static final CurrencyAmount CURRENCY_AMOUNT = CurrencyAmount.of(Currency.USD, 1d); + private static final Map CAPLET_CURRENCY_AMOUNT_MAP = ImmutableMap.of( + CAPLET_LONG, CURRENCY_AMOUNT); + + @Test + void test_of() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertThat(test.getAmounts()).isEqualTo(CAPLET_CURRENCY_AMOUNT_MAP); + } + + @Test + void test_findAmount() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertThat(test.findAmount(CAPLET_LONG)).contains(CURRENCY_AMOUNT); + } + + @Test + void test_findAmountEmpty() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertThat(test.findAmount(CAPLET_SHORT)).isEmpty(); + } + + @Test + void test_getAmount() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertThat(test.getAmount(CAPLET_LONG)).isEqualTo(CURRENCY_AMOUNT); + } + + @Test + void test_getAmountThrows() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertThatIllegalArgumentException().isThrownBy(() -> test.getAmount(CAPLET_SHORT)) + .withMessage("Could not find currency amount for " + CAPLET_SHORT); + } + + @Test + void coverage() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + coverImmutableBean(test); + } + + @Test + public void test_serialization() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts test = + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts.of(CAPLET_CURRENCY_AMOUNT_MAP); + assertSerialization(test); + } + +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricerTest.java new file mode 100644 index 0000000000..7fb3f46f5d --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorLegPricerTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.FLOOR_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.NOTIONAL_VALUE; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES_AFTER; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES_PAY; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS_AFTER; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS_PAY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; + +import java.time.ZoneOffset; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.collect.MapStream; +import com.opengamma.strata.market.param.CurrencyParameterSensitivities; +import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapletFloorletPeriod; + +/** + * Test {@link SabrOvernightInArrearsCapFloorLegPricer}. + */ +public class SabrOvernightInArrearsCapFloorLegPricerTest { + + private static final SabrOvernightInArrearsCapFloorLegPricer PRICER = SabrOvernightInArrearsCapFloorLegPricer.DEFAULT; + private static final SabrOvernightInArrearsCapletFloorletPeriodPricer PRICER_PERIOD = PRICER.getPeriodPricer(); + private static final double TOL = 1.0e-13; + + @Test + public void test_presentValue() { + CurrencyAmount capComputed = PRICER.presentValue(CAP_LEG, RATES, VOLS); + CurrencyAmount floorComputed = PRICER.presentValue(FLOOR_LEG, RATES, VOLS); + double capExpected = 0d; + double floorExpected = 0d; + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 0; i < nPeriods; ++i) { + capExpected += PRICER_PERIOD.presentValue(CAP_LEG.getCapletFloorletPeriods().get(i), RATES, VOLS).getAmount(); + floorExpected += PRICER_PERIOD.presentValue(FLOOR_LEG.getCapletFloorletPeriods().get(i), RATES, VOLS).getAmount(); + } + assertThat(capComputed.getCurrency()).isEqualTo(EUR); + assertThat(capComputed.getAmount()).isEqualTo(capExpected); + assertThat(floorComputed.getCurrency()).isEqualTo(EUR); + assertThat(floorComputed.getAmount()).isEqualTo(floorExpected); + } + + @Test + public void test_presentValue_after() { + CurrencyAmount capComputed = PRICER.presentValue(CAP_LEG, RATES_AFTER, VOLS_AFTER); + CurrencyAmount floorComputed = PRICER.presentValue(FLOOR_LEG, RATES_AFTER, VOLS_AFTER); + double capExpected = 0d; + double floorExpected = 0d; + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 1; i < nPeriods; ++i) { + capExpected += PRICER_PERIOD.presentValue( + CAP_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER).getAmount(); + floorExpected += PRICER_PERIOD.presentValue( + FLOOR_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER).getAmount(); + } + assertThat(capComputed.getCurrency()).isEqualTo(EUR); + assertThat(capComputed.getAmount()).isCloseTo(capExpected, offset(TOL * NOTIONAL_VALUE)); + assertThat(floorComputed.getCurrency()).isEqualTo(EUR); + assertThat(floorComputed.getAmount()).isCloseTo(floorExpected, offset(TOL * NOTIONAL_VALUE)); + } + + //------------------------------------------------------------------------- + @Test + public void test_presentValueCapletFloorletPeriods() { + CurrencyAmount capComputed = PRICER.presentValue(CAP_LEG, RATES, VOLS); + CurrencyAmount floorComputed = PRICER.presentValue(FLOOR_LEG, RATES, VOLS); + Map capletsComputed = + PRICER.presentValueCapletFloorletPeriods(CAP_LEG, RATES, VOLS).getAmounts(); + Map floorletsComputed = + PRICER.presentValueCapletFloorletPeriods(FLOOR_LEG, RATES, VOLS).getAmounts(); + + Map capletsExpected = + MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .mapValues(caplet -> PRICER_PERIOD.presentValue(caplet, RATES, VOLS)) + .toMap(); + CurrencyAmount capletsTotalExpected = capletsExpected.values().stream().reduce(CurrencyAmount::plus).get(); + Map floorletsExpected = + MapStream.of(FLOOR_LEG.getCapletFloorletPeriods()) + .mapValues(floorlet -> PRICER_PERIOD.presentValue(floorlet, RATES, VOLS)) + .toMap(); + CurrencyAmount floorletsTotalExpected = floorletsExpected.values().stream().reduce(CurrencyAmount::plus).get(); + + assertThat(capletsComputed).isEqualTo(capletsExpected); + assertThat(capComputed).isEqualTo(capletsTotalExpected); + assertThat(floorletsComputed).isEqualTo(floorletsExpected); + assertThat(floorComputed).isEqualTo(floorletsTotalExpected); + } + + @Test + public void test_presentValueCapletFloorletPeriods_after() { + CurrencyAmount capComputed = PRICER.presentValue(CAP_LEG, RATES_AFTER, VOLS_AFTER); + CurrencyAmount floorComputed = PRICER.presentValue(FLOOR_LEG, RATES_AFTER, VOLS_AFTER); + Map capletsComputed = + PRICER.presentValueCapletFloorletPeriods(CAP_LEG, RATES_AFTER, VOLS_AFTER).getAmounts(); + Map floorletsComputed = + PRICER.presentValueCapletFloorletPeriods(FLOOR_LEG, RATES_AFTER, VOLS_AFTER).getAmounts(); + + Map capletsExpected = + MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .mapValues(caplet -> PRICER_PERIOD.presentValue(caplet, RATES_AFTER, VOLS_AFTER)) + .toMap(); + CurrencyAmount capletsTotalExpected = capletsExpected.values().stream().reduce(CurrencyAmount::plus).get(); + Map floorletsExpected = + MapStream.of(FLOOR_LEG.getCapletFloorletPeriods()) + .mapValues(floorlet -> PRICER_PERIOD.presentValue(floorlet, RATES_AFTER, VOLS_AFTER)) + .toMap(); + CurrencyAmount floorletsTotalExpected = floorletsExpected.values().stream().reduce(CurrencyAmount::plus).get(); + + assertThat(capletsComputed).isEqualTo(capletsExpected); + assertThat(capComputed).isEqualTo(capletsTotalExpected); + assertThat(floorletsComputed).isEqualTo(floorletsExpected); + assertThat(floorComputed).isEqualTo(floorletsTotalExpected); + } + + //------------------------------------------------------------------------- + @Test + public void test_presentValueSensitivityRatesStickyModel() { + PointSensitivityBuilder capComputed = PRICER.presentValueSensitivityRatesStickyModel(CAP_LEG, RATES, VOLS); + PointSensitivityBuilder floorComputed = PRICER.presentValueSensitivityRatesStickyModel(FLOOR_LEG, RATES, VOLS); + PointSensitivityBuilder capExpected = PointSensitivityBuilder.none(); + PointSensitivityBuilder floorExpected = PointSensitivityBuilder.none(); + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 0; i < nPeriods; ++i) { + capExpected = capExpected.combinedWith( + PRICER_PERIOD.presentValueSensitivityRatesStickyModel( + CAP_LEG.getCapletFloorletPeriods().get(i), + RATES, + VOLS)); + floorExpected = floorExpected.combinedWith( + PRICER_PERIOD.presentValueSensitivityRatesStickyModel( + FLOOR_LEG.getCapletFloorletPeriods().get(i), + RATES, + VOLS)); + } + CurrencyParameterSensitivities capSensiComputed = RATES_AFTER.parameterSensitivity(capComputed.build()); + CurrencyParameterSensitivities floorSensiComputed = RATES_AFTER.parameterSensitivity(floorComputed.build()); + CurrencyParameterSensitivities capSensiExpected = RATES_AFTER.parameterSensitivity(capExpected.build()); + CurrencyParameterSensitivities floorSensiExpected = RATES_AFTER.parameterSensitivity(floorExpected.build()); + assertThat(capSensiComputed.equalWithTolerance(capSensiExpected, NOTIONAL_VALUE * TOL)).isTrue(); + assertThat(floorSensiComputed.equalWithTolerance(floorSensiExpected, NOTIONAL_VALUE * TOL)).isTrue(); + } + + @Test + public void test_presentValueSensitivityRatesStickyModel_after() { + PointSensitivityBuilder capComputed = PRICER.presentValueSensitivityRatesStickyModel( + CAP_LEG, + RATES_AFTER, + VOLS_AFTER); + PointSensitivityBuilder floorComputed = PRICER.presentValueSensitivityRatesStickyModel( + FLOOR_LEG, + RATES_AFTER, + VOLS_AFTER); + PointSensitivityBuilder capExpected = PointSensitivityBuilder.none(); + PointSensitivityBuilder floorExpected = PointSensitivityBuilder.none(); + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 1; i < nPeriods; ++i) { + capExpected = capExpected.combinedWith( + PRICER_PERIOD.presentValueSensitivityRatesStickyModel( + CAP_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER)); + floorExpected = floorExpected.combinedWith( + PRICER_PERIOD.presentValueSensitivityRatesStickyModel( + FLOOR_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER)); + } + CurrencyParameterSensitivities capSensiComputed = RATES_AFTER.parameterSensitivity(capComputed.build()); + CurrencyParameterSensitivities floorSensiComputed = RATES_AFTER.parameterSensitivity(floorComputed.build()); + CurrencyParameterSensitivities capSensiExpected = RATES_AFTER.parameterSensitivity(capExpected.build()); + CurrencyParameterSensitivities floorSensiExpected = RATES_AFTER.parameterSensitivity(floorExpected.build()); + assertThat(capSensiComputed.equalWithTolerance(capSensiExpected, NOTIONAL_VALUE * TOL)).isTrue(); + assertThat(floorSensiComputed.equalWithTolerance(floorSensiExpected, NOTIONAL_VALUE * TOL)).isTrue(); + } + + //------------------------------------------------------------------------- + @Test + public void test_presentValueSensitivityModelParamsSabr() { + PointSensitivityBuilder capComputed = PRICER.presentValueSensitivityModelParamsSabr(CAP_LEG, RATES, VOLS); + PointSensitivityBuilder floorComputed = PRICER.presentValueSensitivityModelParamsSabr(FLOOR_LEG, RATES, VOLS); + CurrencyParameterSensitivities capExpected = CurrencyParameterSensitivities.empty(); + CurrencyParameterSensitivities floorExpected = CurrencyParameterSensitivities.empty(); + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 0; i < nPeriods; ++i) { + capExpected = capExpected.combinedWith(VOLS.parameterSensitivity(PRICER_PERIOD + .presentValueSensitivityModelParamsSabr(CAP_LEG.getCapletFloorletPeriods().get(i), RATES, VOLS).build())); + floorExpected = floorExpected.combinedWith(VOLS.parameterSensitivity(PRICER_PERIOD + .presentValueSensitivityModelParamsSabr(FLOOR_LEG.getCapletFloorletPeriods().get(i), RATES, VOLS).build())); + } + CurrencyParameterSensitivities capSensiComputed = VOLS.parameterSensitivity(capComputed.build()); + CurrencyParameterSensitivities floorSensiComputed = VOLS.parameterSensitivity(floorComputed.build()); + assertThat(capSensiComputed.equalWithTolerance(capExpected, TOL * NOTIONAL_VALUE)).isTrue(); + assertThat(floorSensiComputed.equalWithTolerance(floorExpected, TOL * NOTIONAL_VALUE)).isTrue(); + } + + @Test + public void test_presentValueSensitivityModelParamsSabr_after() { + PointSensitivityBuilder capComputed = PRICER.presentValueSensitivityModelParamsSabr( + CAP_LEG, + RATES_AFTER, + VOLS_AFTER); + PointSensitivityBuilder floorComputed = PRICER.presentValueSensitivityModelParamsSabr( + FLOOR_LEG, + RATES_AFTER, + VOLS_AFTER); + CurrencyParameterSensitivities capExpected = CurrencyParameterSensitivities.empty(); + CurrencyParameterSensitivities floorExpected = CurrencyParameterSensitivities.empty(); + int nPeriods = CAP_LEG.getCapletFloorletPeriods().size(); + for (int i = 1; i < nPeriods; ++i) { + capExpected = capExpected.combinedWith( + VOLS_AFTER.parameterSensitivity(PRICER_PERIOD.presentValueSensitivityModelParamsSabr( + CAP_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER).build())); + floorExpected = floorExpected.combinedWith( + VOLS_AFTER.parameterSensitivity(PRICER_PERIOD.presentValueSensitivityModelParamsSabr( + FLOOR_LEG.getCapletFloorletPeriods().get(i), + RATES_AFTER, + VOLS_AFTER).build())); + } + CurrencyParameterSensitivities capSensiComputed = VOLS_AFTER.parameterSensitivity(capComputed.build()); + CurrencyParameterSensitivities floorSensiComputed = VOLS_AFTER.parameterSensitivity(floorComputed.build()); + assertThat(capSensiComputed.equalWithTolerance(capExpected, TOL * NOTIONAL_VALUE)).isTrue(); + assertThat(floorSensiComputed.equalWithTolerance(floorExpected, TOL * NOTIONAL_VALUE)).isTrue(); + } + + //------------------------------------------------------------------------- + @Test + public void test_currentCash() { + CurrencyAmount capComputed = PRICER.currentCash(CAP_LEG, RATES, VOLS); + CurrencyAmount floorComputed = PRICER.currentCash(FLOOR_LEG, RATES, VOLS); + assertThat(capComputed.getCurrency()).isEqualTo(EUR); + assertThat(capComputed.getAmount()).isEqualTo(0d); + assertThat(floorComputed.getCurrency()).isEqualTo(EUR); + assertThat(floorComputed.getAmount()).isEqualTo(0d); + } + + @Test + public void test_currentCash_pay() { + CurrencyAmount capComputed = PRICER.currentCash(CAP_LEG, RATES_PAY, VOLS_PAY); + CurrencyAmount floorComputed = PRICER.currentCash(FLOOR_LEG, RATES_PAY, VOLS_PAY); + double capExpected = 0d; + OvernightInArrearsCapletFloorletPeriod period = FLOOR_LEG.getCapletFloorletPeriods().get(1); + double floorExpected = PRICER_PERIOD.presentValue(period, RATES_PAY, VOLS_PAY).getAmount(); + assertThat(capComputed.getCurrency()).isEqualTo(EUR); + assertThat(capComputed.getAmount()).isEqualTo(capExpected); + assertThat(floorComputed.getCurrency()).isEqualTo(EUR); + assertThat(floorComputed.getAmount()).isCloseTo(floorExpected, offset(TOL * NOTIONAL_VALUE)); + } + + //------------------------------------------------------------------------- + @Test + public void test_impliedVolatility() { + Map computed = + PRICER.impliedVolatilities(CAP_LEG, RATES, VOLS).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .mapValues(caplet -> PRICER_PERIOD.impliedVolatility(caplet, RATES, VOLS)) + .toMap(); + assertThat(computed).isEqualTo(expected); + computed.forEach((caplet, vol) -> assertThat(vol).isCloseTo(expected.get(caplet), offset(TOL))); + } + + @Test + public void test_impliedVolatility_onFix() { + Map computed = + PRICER.impliedVolatilities(CAP_LEG, RATES_PAY, VOLS_PAY).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .filterKeys(caplet -> VOLS_PAY.relativeTime(caplet.getEndDate().atStartOfDay(ZoneOffset.UTC)) >= 0) + .mapValues(caplet -> PRICER_PERIOD.impliedVolatility(caplet, RATES_PAY, VOLS_PAY)) + .toMap(); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_impliedVolatility_afterFix() { + Map computed = + PRICER.impliedVolatilities(CAP_LEG, RATES_AFTER, VOLS_AFTER).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .filterKeys(caplet -> VOLS_AFTER.relativeTime(caplet.getEndDate().atStartOfDay(ZoneOffset.UTC)) >= 0) + .mapValues(caplet -> PRICER_PERIOD.impliedVolatility(caplet, RATES_AFTER, VOLS_AFTER)) + .toMap(); + assertThat(computed).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void test_forwardRate() { + Map computed = PRICER.forwardRates(CAP_LEG, RATES).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .mapValues(caplet -> PRICER_PERIOD.forwardRate(caplet, RATES)) + .toMap(); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_forwardRate_onFix() { + Map computed = PRICER.forwardRates(CAP_LEG, RATES_PAY).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .filterKeys(caplet -> !RATES_PAY.getValuationDate().isAfter(caplet.getEndDate())) + .mapValues(caplet -> PRICER_PERIOD.forwardRate(caplet, RATES_PAY)) + .toMap(); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_forwardRate_afterFix() { + Map computed = + PRICER.forwardRates(CAP_LEG, RATES_AFTER).getAmounts(); + Map expected = MapStream.of(CAP_LEG.getCapletFloorletPeriods()) + .filterKeys(caplet -> !RATES_AFTER.getValuationDate().isAfter(caplet.getEndDate())) + .mapValues(caplet -> PRICER_PERIOD.forwardRate(caplet, RATES_AFTER)) + .toMap(); + assertThat(computed).isEqualTo(expected); + } + +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricerTest.java new file mode 100644 index 0000000000..d9e43c2715 --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorProductPricerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_ONE_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_TWO_LEGS; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.NOTIONAL_VALUE; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.PAY_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES_PAY; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS_PAY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.MultiCurrencyAmount; +import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; +import com.opengamma.strata.pricer.swap.DiscountingSwapLegPricer; + +/** + * Test {@link SabrOvernightInArrearsCapFloorProductPricer} + */ +public class SabrOvernightInArrearsCapFloorProductPricerTest { + + private static final SabrOvernightInArrearsCapFloorProductPricer PRICER = + SabrOvernightInArrearsCapFloorProductPricer.DEFAULT; + private static final SabrOvernightInArrearsCapFloorLegPricer PRICER_CAP_LEG = PRICER.getCapFloorLegPricer(); + private static final DiscountingSwapLegPricer PRICER_PAY_LEG = PRICER.getPayLegPricer(); + private static final double TOL = 1.0e-13; + + @Test + public void test_presentValue() { + MultiCurrencyAmount computed1 = PRICER.presentValue(CAP_ONE_LEG, RATES, VOLS); + MultiCurrencyAmount computed2 = PRICER.presentValue(CAP_TWO_LEGS, RATES, VOLS); + CurrencyAmount cap = PRICER_CAP_LEG.presentValue(CAP_LEG, RATES, VOLS); + CurrencyAmount pay = PRICER_PAY_LEG.presentValue(PAY_LEG, RATES); + assertThat(computed1).isEqualTo(MultiCurrencyAmount.of(cap)); + assertThat(computed2).isEqualTo(MultiCurrencyAmount.of(cap.plus(pay))); + } + + @Test + public void test_presentValueCapletFloorletPeriods() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts computed1 = + PRICER.presentValueCapletFloorletPeriods(CAP_ONE_LEG, RATES, VOLS); + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts computed2 = + PRICER.presentValueCapletFloorletPeriods(CAP_TWO_LEGS, RATES, VOLS); + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts expected = + PRICER_CAP_LEG.presentValueCapletFloorletPeriods(CAP_LEG, RATES, VOLS); + assertThat(computed1).isEqualTo(expected); + assertThat(computed2).isEqualTo(expected); // calc ignores pay leg pv + } + + @Test + public void test_presentValueSensitivityRatesStickyModel() { + PointSensitivityBuilder computed1 = PRICER.presentValueSensitivityRatesStickyModel(CAP_ONE_LEG, RATES, VOLS); + PointSensitivityBuilder computed2 = PRICER.presentValueSensitivityRatesStickyModel(CAP_TWO_LEGS, RATES, VOLS); + PointSensitivityBuilder cap = PRICER_CAP_LEG.presentValueSensitivityRatesStickyModel(CAP_LEG, RATES, VOLS); + PointSensitivityBuilder pay = PRICER_PAY_LEG.presentValueSensitivity(PAY_LEG, RATES); + assertThat(computed1).isEqualTo(cap); + assertThat(computed2).isEqualTo(cap.combinedWith(pay)); + } + + @Test + public void test_presentValueSensitivityModelParamsSabr() { + PointSensitivityBuilder computed1 = PRICER.presentValueSensitivityModelParamsSabr(CAP_ONE_LEG, RATES, VOLS); + PointSensitivityBuilder computed2 = PRICER.presentValueSensitivityModelParamsSabr(CAP_TWO_LEGS, RATES, VOLS); + PointSensitivityBuilder cap = PRICER_CAP_LEG.presentValueSensitivityModelParamsSabr(CAP_LEG, RATES, VOLS); + assertThat(computed1).isEqualTo(cap); + assertThat(computed2).isEqualTo(cap); + } + + @Test + public void test_currencyExposure() { + MultiCurrencyAmount computed1 = PRICER.currencyExposure(CAP_ONE_LEG, RATES, VOLS); + MultiCurrencyAmount computed2 = PRICER.currencyExposure(CAP_TWO_LEGS, RATES, VOLS); + MultiCurrencyAmount pv1 = PRICER.presentValue(CAP_ONE_LEG, RATES, VOLS); + MultiCurrencyAmount pv2 = PRICER.presentValue(CAP_TWO_LEGS, RATES, VOLS); + PointSensitivityBuilder point1 = PRICER.presentValueSensitivityRatesStickyModel(CAP_ONE_LEG, RATES, VOLS); + PointSensitivityBuilder point2 = PRICER.presentValueSensitivityRatesStickyModel(CAP_TWO_LEGS, RATES, VOLS); + MultiCurrencyAmount expected1 = RATES.currencyExposure(point1.build()).plus(pv1); + MultiCurrencyAmount expected2 = RATES.currencyExposure(point2.build()).plus(pv2); + assertThat(computed1.getAmount(EUR).getAmount()) + .isCloseTo(expected1.getAmount(EUR).getAmount(), offset(NOTIONAL_VALUE * TOL)); + assertThat(computed2.getAmount(EUR).getAmount()) + .isCloseTo(expected2.getAmount(EUR).getAmount(), offset(NOTIONAL_VALUE * TOL)); + } + + @Test + public void test_currentCash() { + MultiCurrencyAmount cc1 = PRICER.currentCash(CAP_ONE_LEG, RATES, VOLS); + MultiCurrencyAmount cc2 = PRICER.currentCash(CAP_TWO_LEGS, RATES, VOLS); + assertThat(cc1).isEqualTo(MultiCurrencyAmount.of(CurrencyAmount.zero(EUR))); + assertThat(cc2).isEqualTo(MultiCurrencyAmount.of(CurrencyAmount.zero(EUR))); + } + + @Test + public void test_currentCash_onPay() { + MultiCurrencyAmount cc1 = PRICER.currentCash(CAP_ONE_LEG, RATES_PAY, VOLS_PAY); + MultiCurrencyAmount cc2 = PRICER.currentCash(CAP_TWO_LEGS, RATES_PAY, VOLS_PAY); + CurrencyAmount ccCap = PRICER_CAP_LEG.currentCash(CAP_LEG, RATES_PAY, VOLS_PAY); + CurrencyAmount ccPay = PRICER_PAY_LEG.currentCash(PAY_LEG, RATES_PAY); + assertThat(cc1).isEqualTo(MultiCurrencyAmount.of(ccCap)); + assertThat(cc2).isEqualTo(MultiCurrencyAmount.of(ccCap).plus(ccPay)); + } + + //------------------------------------------------------------------------- + @Test + public void test_impliedVolatility() { + OvernightInArrearsCapletFloorletPeriodAmounts computed = PRICER.impliedVolatilities(CAP_ONE_LEG, RATES, VOLS); + OvernightInArrearsCapletFloorletPeriodAmounts expected = PRICER_CAP_LEG.impliedVolatilities(CAP_LEG, RATES, VOLS); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_impliedVolatility_onFix() { + OvernightInArrearsCapletFloorletPeriodAmounts computed = PRICER.impliedVolatilities( + CAP_ONE_LEG, + RATES_PAY, + VOLS_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expected = PRICER_CAP_LEG.impliedVolatilities( + CAP_LEG, + RATES_PAY, + VOLS_PAY); + assertThat(computed).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + @Test + public void test_forwardRate() { + OvernightInArrearsCapletFloorletPeriodAmounts computed = PRICER.forwardRates(CAP_ONE_LEG, RATES); + OvernightInArrearsCapletFloorletPeriodAmounts expected = PRICER_CAP_LEG.forwardRates(CAP_LEG, RATES); + assertThat(computed).isEqualTo(expected); + } + + @Test + public void test_forwardRate_onFix() { + OvernightInArrearsCapletFloorletPeriodAmounts computed = PRICER.forwardRates(CAP_ONE_LEG, RATES_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expected = PRICER_CAP_LEG.forwardRates(CAP_LEG, RATES_PAY); + assertThat(computed).isEqualTo(expected); + } + +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTestData.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTestData.java new file mode 100644 index 0000000000..71924f58d4 --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTestData.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; +import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; +import static com.opengamma.strata.collect.TestHelper.dateUtc; +import static com.opengamma.strata.product.common.PayReceive.PAY; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.currency.Payment; +import com.opengamma.strata.basics.date.BusinessDayAdjustment; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DayCounts; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.date.HolidayCalendar; +import com.opengamma.strata.basics.date.PeriodAdditionConventions; +import com.opengamma.strata.basics.date.Tenor; +import com.opengamma.strata.basics.date.TenorAdjustment; +import com.opengamma.strata.basics.index.IborIndex; +import com.opengamma.strata.basics.index.ImmutableIborIndex; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.PeriodicSchedule; +import com.opengamma.strata.basics.schedule.RollConventions; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.basics.value.ValueSchedule; +import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries; +import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeriesBuilder; +import com.opengamma.strata.pricer.rate.ImmutableRatesProvider; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.capfloor.OvernightInArrearsCapFloorLeg; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloor; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloorLeg; +import com.opengamma.strata.product.capfloor.ResolvedOvernightInArrearsCapFloorTrade; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.swap.OvernightRateCalculation; +import com.opengamma.strata.product.swap.ResolvedSwapLeg; + +/** + * Data for testing overnight rate in arrears cap/floor. + */ +public class SabrOvernightInArrearsCapFloorTestData { + + private static final ZonedDateTime VALUATION = dateUtc(2021, 12, 20); + private static final ZonedDateTime VALUATION_AFTER = dateUtc(2023, 8, 18); + private static final ZonedDateTime VALUATION_PAY = dateUtc(2024, 6, 24); + // reference data + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final IborIndex EUR_ESTRTERM_3M = ImmutableIborIndex.builder() + .name("EUR-ESTRTERM-3M") + .currency(EUR) + .dayCount(DayCounts.ACT_360) + .fixingCalendar(EUTA) + .fixingTime(LocalTime.of(11, 0)) + .fixingZone(ZoneId.of("Europe/Brussels")) + .fixingDateOffset(DaysAdjustment.ofBusinessDays(-2, EUTA)) + .effectiveDateOffset(DaysAdjustment.ofBusinessDays(2, EUTA)) + .maturityDateOffset(TenorAdjustment.of( + Tenor.TENOR_3M, + PeriodAdditionConventions.LAST_BUSINESS_DAY, + BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, EUTA))) + .build(); + // cap leg and floor leg + private static final HolidayCalendar EUTA_IMPL = REF_DATA.getValue(EUTA); + private static final LocalDate START_DATE = LocalDate.of(2022, 6, 22); + private static final LocalDate END_DATE = LocalDate.of(2027, 6, 22); + static final double NOTIONAL_VALUE = 1_000_000.0d; + private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONAL_VALUE); + private static final double STRIKE = 0.0155; + private static final ValueSchedule STRIKE_SCHEDULE = ValueSchedule.of(STRIKE); + private static final PeriodicSchedule PAY_SCHEDULE = PeriodicSchedule.of( + START_DATE, + END_DATE, + Frequency.P12M, + BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, EUTA), + StubConvention.NONE, + RollConventions.NONE); + static final ResolvedOvernightInArrearsCapFloorLeg CAP_LEG = OvernightInArrearsCapFloorLeg.builder() + .capSchedule(STRIKE_SCHEDULE) + .calculation(OvernightRateCalculation.of(EUR_ESTR)) + .currency(EUR) + .notional(NOTIONAL) + .paymentSchedule(PAY_SCHEDULE) + .payReceive(PayReceive.RECEIVE) + .build().resolve(REF_DATA); + static final ResolvedOvernightInArrearsCapFloorLeg FLOOR_LEG = OvernightInArrearsCapFloorLeg.builder() + .floorSchedule(STRIKE_SCHEDULE) + .calculation(OvernightRateCalculation.of(EUR_ESTR)) + .currency(EUR) + .notional(NOTIONAL) + .paymentSchedule(PAY_SCHEDULE) + .payReceive(PayReceive.PAY) + .build().resolve(REF_DATA); + // caps + static final ResolvedSwapLeg PAY_LEG = IborCapFloorDataSet.createFixedPayLeg( + EUR_ESTRTERM_3M, + START_DATE, + END_DATE, + 0.0015, + NOTIONAL_VALUE, + PAY); + static final ResolvedOvernightInArrearsCapFloor CAP_TWO_LEGS = + ResolvedOvernightInArrearsCapFloor.of(CAP_LEG, PAY_LEG); + static final ResolvedOvernightInArrearsCapFloor CAP_ONE_LEG = ResolvedOvernightInArrearsCapFloor.of(CAP_LEG); + // cap trades + private static final TradeInfo TRADE_INFO = TradeInfo.builder().tradeDate(VALUATION.toLocalDate()).build(); + static final Payment PREMIUM = Payment.of(EUR, -NOTIONAL_VALUE * 0.19, VALUATION.toLocalDate()); + static final ResolvedOvernightInArrearsCapFloorTrade TRADE = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(CAP_ONE_LEG) + .build(); + static final ResolvedOvernightInArrearsCapFloorTrade TRADE_PAYLEG = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(CAP_TWO_LEGS) + .info(TRADE_INFO) + .build(); + static final ResolvedOvernightInArrearsCapFloorTrade TRADE_PREMIUM = ResolvedOvernightInArrearsCapFloorTrade.builder() + .product(CAP_ONE_LEG) + .premium(PREMIUM) + .info(TradeInfo.empty()) + .build(); + // valuation date before start date + static final ImmutableRatesProvider RATES = IborCapletFloorletSabrRateVolatilityDataSet.getRatesProvider( + VALUATION.toLocalDate(), + EUR_ESTRTERM_3M, + LocalDateDoubleTimeSeries.empty()); + static final SabrParametersIborCapletFloorletVolatilities VOLS = IborCapletFloorletSabrRateVolatilityDataSet + .getVolatilities(VALUATION, EUR_ESTRTERM_3M); + // valuation datas after start data + private static final LocalDateDoubleTimeSeries TS_ESTR_AFTER_END; + static { + LocalDateDoubleTimeSeriesBuilder builder = LocalDateDoubleTimeSeries.builder(); + LocalDate currentDate = START_DATE; + while (currentDate.isBefore(VALUATION_PAY.toLocalDate())) { + builder.put(currentDate, 0.0150); + currentDate = EUTA_IMPL.next(currentDate); + } + TS_ESTR_AFTER_END = builder.build(); + } + static final ImmutableRatesProvider RATES_AFTER = + IborCapletFloorletSabrRateVolatilityDataSet.getRatesProvider( + VALUATION_AFTER.toLocalDate(), + EUR_ESTRTERM_3M, + LocalDateDoubleTimeSeries.empty()).toBuilder() + .timeSeries(EUR_ESTR, TS_ESTR_AFTER_END) + .build(); + static final SabrParametersIborCapletFloorletVolatilities VOLS_AFTER = + IborCapletFloorletSabrRateVolatilityDataSet.getVolatilities(VALUATION_AFTER, EUR_ESTRTERM_3M); + static final ImmutableRatesProvider RATES_PAY = + IborCapletFloorletSabrRateVolatilityDataSet.getRatesProvider( + VALUATION_PAY.toLocalDate(), + EUR_ESTRTERM_3M, + LocalDateDoubleTimeSeries.empty()).toBuilder() + .timeSeries(EUR_ESTR, TS_ESTR_AFTER_END) + .build(); + static final SabrParametersIborCapletFloorletVolatilities VOLS_PAY = + IborCapletFloorletSabrRateVolatilityDataSet.getVolatilities(VALUATION_PAY, EUR_ESTRTERM_3M); + + private SabrOvernightInArrearsCapFloorTestData() { + } + +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricerTest.java new file mode 100644 index 0000000000..2ddc9d2929 --- /dev/null +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapFloorTradePricerTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.pricer.capfloor; + +import static com.opengamma.strata.basics.currency.Currency.EUR; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_ONE_LEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.CAP_TWO_LEGS; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.NOTIONAL_VALUE; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.PREMIUM; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.RATES_PAY; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.TRADE; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.TRADE_PAYLEG; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.TRADE_PREMIUM; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS; +import static com.opengamma.strata.pricer.capfloor.SabrOvernightInArrearsCapFloorTestData.VOLS_PAY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; + +import org.junit.jupiter.api.Test; + +import com.opengamma.strata.basics.currency.CurrencyAmount; +import com.opengamma.strata.basics.currency.MultiCurrencyAmount; +import com.opengamma.strata.market.sensitivity.PointSensitivities; +import com.opengamma.strata.pricer.DiscountingPaymentPricer; + +/** + * Test {@link SabrOvernightInArrearsCapFloorTradePricer}. + */ +public class SabrOvernightInArrearsCapFloorTradePricerTest { + + private static final SabrOvernightInArrearsCapFloorTradePricer PRICER = + SabrOvernightInArrearsCapFloorTradePricer.DEFAULT; + private static final SabrOvernightInArrearsCapFloorProductPricer PRICER_PRODUCT = PRICER.getProductPricer(); + private static final DiscountingPaymentPricer PRICER_PREMIUM = PRICER.getPaymentPricer(); + private static final double TOL = 1.0e-13; + + @Test + public void test_presentValue() { + MultiCurrencyAmount computedWithPayLeg = PRICER.presentValue(TRADE_PAYLEG, RATES, VOLS); + MultiCurrencyAmount computedWithPremium = PRICER.presentValue(TRADE_PREMIUM, RATES, VOLS); + MultiCurrencyAmount pvOneLeg = PRICER_PRODUCT.presentValue(CAP_ONE_LEG, RATES, VOLS); + MultiCurrencyAmount pvTwoLegs = PRICER_PRODUCT.presentValue(CAP_TWO_LEGS, RATES, VOLS); + CurrencyAmount pvPrem = PRICER_PREMIUM.presentValue(PREMIUM, RATES); + assertThat(computedWithPayLeg).isEqualTo(pvTwoLegs); + assertThat(computedWithPremium).isEqualTo(pvOneLeg.plus(pvPrem)); + } + + @Test + public void test_presentValueCapletFloorletPeriods() { + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts computed = PRICER.presentValueCapletFloorletPeriods( + TRADE, + RATES, + VOLS); + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts computedWithPayLeg = PRICER.presentValueCapletFloorletPeriods( + TRADE_PAYLEG, + RATES, + VOLS); + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts computedWithPremium = PRICER.presentValueCapletFloorletPeriods( + TRADE_PREMIUM, + RATES, + VOLS); + OvernightInArrearsCapletFloorletPeriodCurrencyAmounts expected = PRICER_PRODUCT.presentValueCapletFloorletPeriods( + CAP_ONE_LEG, + RATES, + VOLS); + assertThat(computed).isEqualTo(expected); + assertThat(computedWithPayLeg).isEqualTo(expected); // calc ignores pay leg pv + assertThat(computedWithPremium).isEqualTo(expected); // calc ignores premium pv + } + + @Test + public void test_presentValueSensitivityRatesStickyModel() { + PointSensitivities computedWithPayLeg = PRICER.presentValueSensitivityRatesStickyModel(TRADE_PAYLEG, RATES, VOLS); + PointSensitivities computedWithPremium = PRICER.presentValueSensitivityRatesStickyModel(TRADE_PREMIUM, RATES, VOLS); + PointSensitivities pvOneLeg = PRICER_PRODUCT.presentValueSensitivityRatesStickyModel( + CAP_ONE_LEG, + RATES, + VOLS).build(); + PointSensitivities pvTwoLegs = PRICER_PRODUCT.presentValueSensitivityRatesStickyModel( + CAP_TWO_LEGS, + RATES, + VOLS).build(); + PointSensitivities pvPrem = PRICER_PREMIUM.presentValueSensitivity(PREMIUM, RATES).build(); + assertThat(computedWithPayLeg).isEqualTo(pvTwoLegs); + assertThat(computedWithPremium).isEqualTo(pvOneLeg.combinedWith(pvPrem)); + } + + @Test + public void test_presentValueSensitivityModelParamsSabr() { + PointSensitivities computedWithPayLeg = PRICER.presentValueSensitivityModelParamsSabr( + TRADE_PAYLEG, + RATES, + VOLS).build(); + PointSensitivities computedWithPremium = PRICER.presentValueSensitivityModelParamsSabr( + TRADE_PREMIUM, + RATES, + VOLS).build(); + PointSensitivities pvOneLeg = PRICER_PRODUCT.presentValueSensitivityModelParamsSabr( + CAP_ONE_LEG, + RATES, + VOLS).build(); + PointSensitivities pvTwoLegs = PRICER_PRODUCT.presentValueSensitivityModelParamsSabr( + CAP_TWO_LEGS, + RATES, + VOLS).build(); + assertThat(computedWithPayLeg).isEqualTo(pvTwoLegs); + assertThat(computedWithPremium).isEqualTo(pvOneLeg); + } + + @Test + public void test_currencyExposure() { + MultiCurrencyAmount computedWithPayLeg = PRICER.currencyExposure(TRADE_PAYLEG, RATES, VOLS); + MultiCurrencyAmount computedWithPremium = PRICER.currencyExposure(TRADE_PREMIUM, RATES, VOLS); + MultiCurrencyAmount pvWithPayLeg = PRICER.presentValue(TRADE_PAYLEG, RATES, VOLS); + MultiCurrencyAmount pvWithPremium = PRICER.presentValue(TRADE_PREMIUM, RATES, VOLS); + PointSensitivities pointWithPayLeg = PRICER.presentValueSensitivityRatesStickyModel(TRADE_PAYLEG, RATES, VOLS); + PointSensitivities pointWithPremium = PRICER.presentValueSensitivityRatesStickyModel(TRADE_PREMIUM, RATES, VOLS); + MultiCurrencyAmount expectedWithPayLeg = RATES.currencyExposure(pointWithPayLeg).plus(pvWithPayLeg); + MultiCurrencyAmount expectedWithPremium = RATES.currencyExposure(pointWithPremium).plus(pvWithPremium); + assertThat(computedWithPayLeg.getAmount(EUR).getAmount()).isCloseTo( + expectedWithPayLeg.getAmount(EUR).getAmount(), + offset(NOTIONAL_VALUE * TOL)); + assertThat(computedWithPremium.getAmount(EUR).getAmount()).isCloseTo( + expectedWithPremium.getAmount(EUR).getAmount(), + offset(NOTIONAL_VALUE * TOL)); + } + + @Test + public void test_currentCash() { + MultiCurrencyAmount computedWithPayLeg = PRICER.currentCash(TRADE_PAYLEG, RATES, VOLS); + MultiCurrencyAmount computedWithPremium = PRICER.currentCash(TRADE_PREMIUM, RATES, VOLS); + assertThat(computedWithPayLeg).isEqualTo(MultiCurrencyAmount.of(CurrencyAmount.zero(EUR))); + assertThat(computedWithPremium).isEqualTo(MultiCurrencyAmount.of(PREMIUM.getValue())); + } + + @Test + public void test_currentCash_onPay() { + MultiCurrencyAmount computedWithPayLeg = PRICER.currentCash(TRADE_PAYLEG, RATES_PAY, VOLS_PAY); + MultiCurrencyAmount computedWithPremium = PRICER.currentCash(TRADE_PREMIUM, RATES_PAY, VOLS_PAY); + MultiCurrencyAmount expectedWithPayLeg = PRICER_PRODUCT.currentCash(CAP_TWO_LEGS, RATES_PAY, VOLS_PAY); + MultiCurrencyAmount expectedWithPremium = PRICER_PRODUCT.currentCash(CAP_ONE_LEG, RATES_PAY, VOLS_PAY); + assertThat(computedWithPayLeg).isEqualTo(expectedWithPayLeg); + assertThat(computedWithPremium).isEqualTo(expectedWithPremium); + } + + @Test + public void test_forwardRates() { + OvernightInArrearsCapletFloorletPeriodAmounts computedWithPayLeg = PRICER.forwardRates(TRADE_PAYLEG, RATES_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts computedWithPremium = PRICER.forwardRates(TRADE_PREMIUM, RATES_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expectedWithPayLeg = PRICER_PRODUCT.forwardRates( + CAP_TWO_LEGS, + RATES_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expectedWithPremium = PRICER_PRODUCT.forwardRates( + CAP_ONE_LEG, + RATES_PAY); + assertThat(computedWithPayLeg).isEqualTo(expectedWithPayLeg); + assertThat(computedWithPremium).isEqualTo(expectedWithPremium); + } + + @Test + public void test_impliedVolatilities() { + OvernightInArrearsCapletFloorletPeriodAmounts computedWithPayLeg = PRICER.impliedVolatilities( + TRADE_PAYLEG, + RATES_PAY, + VOLS_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts computedWithPremium = PRICER.impliedVolatilities( + TRADE_PREMIUM, + RATES_PAY, + VOLS_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expectedWithPayLeg = PRICER_PRODUCT.impliedVolatilities( + CAP_TWO_LEGS, + RATES_PAY, + VOLS_PAY); + OvernightInArrearsCapletFloorletPeriodAmounts expectedWithPremium = PRICER_PRODUCT.impliedVolatilities( + CAP_ONE_LEG, + RATES_PAY, + VOLS_PAY); + assertThat(computedWithPayLeg).isEqualTo(expectedWithPayLeg); + assertThat(computedWithPremium).isEqualTo(expectedWithPremium); + } + +} diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricerTest.java index ff700c9735..04603fdb77 100644 --- a/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricerTest.java +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/capfloor/SabrOvernightInArrearsCapletFloorletPeriodPricerTest.java @@ -9,11 +9,14 @@ import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; import static com.opengamma.strata.basics.index.OvernightIndices.EUR_ESTR; import static com.opengamma.strata.collect.TestHelper.dateUtc; +import static com.opengamma.strata.pricer.capfloor.IborCapletFloorletSabrRateVolatilityDataSet.CONST_SHIFT; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import org.assertj.core.data.Offset; @@ -427,7 +430,6 @@ public void presentValue_afterpay_formula() { assertThat(computedCaplet.getAmount()).isCloseTo(0.0d, TOLERANCE_PV); assertThat(computedFloorlet.getCurrency()).isEqualTo(EUR); assertThat(computedFloorlet.getAmount()).isCloseTo(0.0d, TOLERANCE_PV); - } //------------------------------------------------------------------------- @@ -586,7 +588,6 @@ public void presentValue_afterend_param_sensitivity() { .presentValueSensitivityModelParamsSabr(FLOORLET_SHORT, RATES_AFTER_END, VOLS_AFTER_END).build(); assertThat(ptsCapletLongComputed).isEqualTo(PointSensitivities.empty()); assertThat(ptsFloorletShortComputed).isEqualTo(PointSensitivities.empty()); - } @Test @@ -597,7 +598,102 @@ public void presentValue_afterpay_param_sensitivity() { .presentValueSensitivityModelParamsSabr(FLOORLET_SHORT, RATES_AFTER_PAY, VOLS_AFTER_PAY).build(); assertThat(ptsCapletLongComputed).isEqualTo(PointSensitivities.empty()); assertThat(ptsFloorletShortComputed).isEqualTo(PointSensitivities.empty()); + } + + //------------------------------------------------------------------------- + + @Test + public void forwardRate_impliedVolatility_beforestart() { + double forwardRateCapletComputed = PRICER_ON_INARREARS_Q1.forwardRate(CAPLET_LONG, RATES); + double impliedVolCapletComputed = PRICER_ON_INARREARS_Q1.impliedVolatility(CAPLET_LONG, RATES, VOLS); + double forwardRateFloorletComputed = PRICER_ON_INARREARS_Q1.forwardRate(FLOORLET_LONG, RATES); + double impliedVolFloorletComputed = PRICER_ON_INARREARS_Q1.impliedVolatility(FLOORLET_LONG, RATES, VOLS); + OvernightIndexObservation onObs = OvernightIndexObservation.of(EUR_ESTR, START_DATE, REF_DATA); + double forwardExpected = RATES.overnightIndexRates(EUR_ESTR).periodRate(onObs, END_DATE); + assertThat(forwardRateCapletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + assertThat(forwardRateFloorletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + double num = NOTIONAL * ACCRUAL_FACTOR * RATES.discountFactor(EUR, PAYMENT_DATE); + double timeToExpiry = VOLS.relativeTime(END_DATE.atStartOfDay(ZoneOffset.UTC)); + CurrencyAmount inArrearsPv = PRICER_ON_INARREARS_Q1.presentValue(CAPLET_LONG, RATES, VOLS); + double impliedVolExpected = BlackFormulaRepository.impliedVolatility( + inArrearsPv.getAmount() / num, + forwardExpected + CONST_SHIFT, + STRIKE + CONST_SHIFT, + timeToExpiry, + true); + assertThat(impliedVolCapletComputed).isCloseTo(impliedVolExpected, TOLERANCE_SMALL_IV); + assertThat(impliedVolFloorletComputed).isCloseTo(impliedVolExpected, TOLERANCE_SMALL_IV); + } + + @Test + public void forwardRate_impliedVolatility_afterstart() { + double strike = 0.0115; // near-ATM + OvernightInArrearsCapletFloorletPeriod capletLong = + OvernightInArrearsCapletFloorletPeriod.builder() + .caplet(strike) + .startDate(START_DATE) + .endDate(END_DATE) + .paymentDate(PAYMENT_DATE) + .yearFraction(ACCRUAL_FACTOR) + .notional(NOTIONAL) + .overnightRate(RATE_COMP) + .build(); + OvernightInArrearsCapletFloorletPeriod floorletLong = + OvernightInArrearsCapletFloorletPeriod.builder() + .floorlet(strike) + .startDate(START_DATE) + .endDate(END_DATE) + .paymentDate(PAYMENT_DATE) + .yearFraction(ACCRUAL_FACTOR) + .notional(NOTIONAL) + .overnightRate(RATE_COMP) + .build(); + double forwardRateCapletComputed = PRICER_ON_INARREARS_Q1.forwardRate(capletLong, RATES_AFTER_START); + double impliedVolCapletComputed = PRICER_ON_INARREARS_Q1.impliedVolatility( + capletLong, + RATES_AFTER_START, + VOLS_AFTER_START); + double forwardRateFloorletComputed = PRICER_ON_INARREARS_Q1.forwardRate(floorletLong, RATES_AFTER_START); + double impliedVolFloorletComputed = PRICER_ON_INARREARS_Q1.impliedVolatility( + floorletLong, + RATES_AFTER_START, + VOLS_AFTER_START); + double forwardExpected = ForwardOvernightCompoundedRateComputationFn.DEFAULT.rate( + capletLong.getOvernightRate(), + START_DATE, + END_DATE, + RATES_AFTER_START); + assertThat(forwardRateCapletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + assertThat(forwardRateFloorletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + double num = NOTIONAL * ACCRUAL_FACTOR * RATES_AFTER_START.discountFactor(EUR, PAYMENT_DATE); + double timeToExpiry = VOLS_AFTER_START.relativeTime(END_DATE.atStartOfDay(ZoneOffset.UTC)); + CurrencyAmount inArrearsPv = PRICER_ON_INARREARS_Q1.presentValue(capletLong, RATES_AFTER_START, VOLS_AFTER_START); + double impliedVolExpected = BlackFormulaRepository.impliedVolatility( + inArrearsPv.getAmount() / num, + forwardExpected + CONST_SHIFT, + strike + CONST_SHIFT, + timeToExpiry, + true); + assertThat(impliedVolCapletComputed).isCloseTo(impliedVolExpected, TOLERANCE_SMALL_IV); + assertThat(impliedVolFloorletComputed).isCloseTo(impliedVolExpected, TOLERANCE_SMALL_IV); + } + @Test + public void forwardRate_impliedVolatility_afterend() { + double forwardRateCapletComputed = PRICER_ON_INARREARS_Q1.forwardRate(CAPLET_LONG, RATES_AFTER_END); + double forwardRateFloorletComputed = PRICER_ON_INARREARS_Q1.forwardRate(FLOORLET_LONG, RATES_AFTER_END); + double forwardExpected = ForwardOvernightCompoundedRateComputationFn.DEFAULT.rate( + CAPLET_LONG.getOvernightRate(), + START_DATE, + END_DATE, + RATES_AFTER_END); + assertThat(forwardRateCapletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + assertThat(forwardRateFloorletComputed).isCloseTo(forwardExpected, TOLERANCE_SMALL_IV); + // impliedVolatility fails after expiry + assertThatIllegalArgumentException() + .isThrownBy(() -> PRICER_ON_INARREARS_Q1.impliedVolatility(CAPLET_LONG, RATES_AFTER_END, VOLS_AFTER_END)); + assertThatIllegalArgumentException() + .isThrownBy(() -> PRICER_ON_INARREARS_Q1.impliedVolatility(FLOORLET_LONG, RATES_AFTER_END, VOLS_AFTER_END)); } } From bd1d98a3ab4f22058dcffebe3ee80e380af4e0cd Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Thu, 17 Oct 2024 14:39:58 +0000 Subject: [PATCH 13/29] [maven-release-plugin] prepare release v2.12.43 --- examples/pom.xml | 6 +++++- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 8f504e9229..c4c542006e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.43-SNAPSHOT + 2.12.43 jar Strata-Examples Example code to demonstrate use of Strata @@ -272,4 +272,8 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> + + + v2.12.43 + diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index da026b9501..5a1ecc949e 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index af02a59f04..497a8ef49e 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index fee1aab278..35ce20a409 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 493539c4e7..384f1354cf 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 60b62bbfb8..31686c8b52 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index a2fc6b5103..b1fac8ad42 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index 50d0d221a7..a741fc8eec 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 32348ee22b..8b73d37ac8 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 5a2cd0de67..5c3679df30 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.43 diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index 9402f178cc..e4b9202891 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 3598b4c230..f692cd3d7c 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index dd1356d3ee..0e274533db 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43-SNAPSHOT + 2.12.43 .. strata-report diff --git a/pom.xml b/pom.xml index 6ced91402f..fae8c87393 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.43-SNAPSHOT + 2.12.43 pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.43 From 56505f6dc99d1a669783315057aed417074c0cc1 Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Thu, 17 Oct 2024 14:40:00 +0000 Subject: [PATCH 14/29] [maven-release-plugin] prepare for next development iteration --- examples/pom.xml | 6 +----- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index c4c542006e..b968607eb1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.43 + 2.12.44-SNAPSHOT jar Strata-Examples Example code to demonstrate use of Strata @@ -272,8 +272,4 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> - - - v2.12.43 - diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 5a1ecc949e..0e84c7ab2a 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 497a8ef49e..2d73016809 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 35ce20a409..66951bea44 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 384f1354cf..8365ff3e38 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 31686c8b52..e0aab3eb48 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index b1fac8ad42..31055089ac 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index a741fc8eec..608c0b2bdd 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 8b73d37ac8..d4721fa8e6 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 5c3679df30..b93dddd2d5 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.43 + HEAD diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index e4b9202891..1a71a31ad0 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index f692cd3d7c..3b4e1150cd 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index 0e274533db..d1b2cd2a47 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.43 + 2.12.44-SNAPSHOT .. strata-report diff --git a/pom.xml b/pom.xml index fae8c87393..836e807683 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.43 + 2.12.44-SNAPSHOT pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.43 + HEAD From 1809ab814fb86a5376421445dcdadda606e14025 Mon Sep 17 00:00:00 2001 From: Andras Vig <93195277+Andras1022@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:08:56 +0100 Subject: [PATCH 15/29] Adding Inauguration Day to MXMC (#2682) * Adding Inauguration Day to MXMC * Adding Inauguration Day to MXMC * Adding Inauguration Day to MXMC --- .../basics/date/GlobalHolidayCalendars.bin | Bin 178050 -> 178050 bytes .../basics/date/GlobalHolidayCalendars.bin | Bin 178050 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin diff --git a/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin b/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin index 8dc6a3d4dab3cdb83346bbe619939cb7b8105fdb..5599a791a5c8ee428b52049b0ad8b59064fbcd2c 100644 GIT binary patch delta 83 zcmZpA&(-vvt6>YH=@Z6t+s&RZ$}n_1FP%mnP`}ub1W40X?@;+5xlym-g8KT(=_Z0h0xn_2B_`w=D7jR>hgp BE)oC$ diff --git a/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin b/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin deleted file mode 100644 index 5599a791a5c8ee428b52049b0ad8b59064fbcd2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178050 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 From 43b95792812784295ed1bd70d90568ca89cdf3ae Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Mon, 28 Oct 2024 16:09:07 +0000 Subject: [PATCH 16/29] [maven-release-plugin] prepare release v2.12.44 --- examples/pom.xml | 6 +++++- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index b968607eb1..d73e14bf52 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.44-SNAPSHOT + 2.12.44 jar Strata-Examples Example code to demonstrate use of Strata @@ -272,4 +272,8 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> + + + v2.12.44 + diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 0e84c7ab2a..f7daff8fd8 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 2d73016809..78d102bc92 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 66951bea44..65654da503 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 8365ff3e38..e53684e9bc 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index e0aab3eb48..1f8cc9940f 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 31055089ac..7573e4fb13 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index 608c0b2bdd..aac4537fcf 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index d4721fa8e6..443ac8045e 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index b93dddd2d5..5dd94b3cb7 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.44 diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index 1a71a31ad0..52471bbda7 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 3b4e1150cd..4fda21b11b 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index d1b2cd2a47..86e23af80e 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44-SNAPSHOT + 2.12.44 .. strata-report diff --git a/pom.xml b/pom.xml index 836e807683..78f49120a4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.44-SNAPSHOT + 2.12.44 pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.44 From 1b23c7a4d638b7928bfe92b716af4ea119e38df7 Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Mon, 28 Oct 2024 16:09:09 +0000 Subject: [PATCH 17/29] [maven-release-plugin] prepare for next development iteration --- examples/pom.xml | 6 +----- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index d73e14bf52..bc2cb9f54b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.44 + 2.12.45-SNAPSHOT jar Strata-Examples Example code to demonstrate use of Strata @@ -272,8 +272,4 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> - - - v2.12.44 - diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index f7daff8fd8..6943914c0b 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 78d102bc92..889f95b5dc 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 65654da503..404ae8ea25 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index e53684e9bc..25ce33b9bb 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 1f8cc9940f..e2b1ccca95 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 7573e4fb13..9be4f8726c 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index aac4537fcf..0b1f5a2fee 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 443ac8045e..2a6b44e568 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 5dd94b3cb7..35776c8892 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.44 + HEAD diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index 52471bbda7..957e9895cd 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 4fda21b11b..660fce4cfe 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index 86e23af80e..5d5c5a7ab6 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.44 + 2.12.45-SNAPSHOT .. strata-report diff --git a/pom.xml b/pom.xml index 78f49120a4..1b6b05e7b2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.44 + 2.12.45-SNAPSHOT pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.44 + HEAD From 4be4dfd075927341859ef331905a350ae4fe6c02 Mon Sep 17 00:00:00 2001 From: Michael Rollins Date: Thu, 21 Nov 2024 16:43:56 +0000 Subject: [PATCH 18/29] Adding XSFA Exchange (#2684) Co-authored-by: Michael Rollins --- .../java/com/opengamma/strata/product/common/ExchangeIds.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/product/src/main/java/com/opengamma/strata/product/common/ExchangeIds.java b/modules/product/src/main/java/com/opengamma/strata/product/common/ExchangeIds.java index 3a361a94c3..5d34c0a450 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/common/ExchangeIds.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/common/ExchangeIds.java @@ -81,6 +81,9 @@ public final class ExchangeIds { /** JSE - Equity Derivatives Market. */ public static final ExchangeId XSAF = ExchangeId.of("XSAF"); + /** JSE - Commodity Derivatives Market. */ + public static final ExchangeId XSFA = ExchangeId.of("XSFA"); + /** Mercado Español de Futuros Financiero (MEFF). */ public static final ExchangeId XMRV = ExchangeId.of("XMRV"); From 259bb0127439e79e1e691beff9bd9ecca6844fd5 Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Thu, 21 Nov 2024 16:53:19 +0000 Subject: [PATCH 19/29] [maven-release-plugin] prepare release v2.12.45 --- examples/pom.xml | 6 +++++- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index bc2cb9f54b..aa82d26b2f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.45-SNAPSHOT + 2.12.45 jar Strata-Examples Example code to demonstrate use of Strata @@ -272,4 +272,8 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> + + + v2.12.45 + diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 6943914c0b..8727d3c33a 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 889f95b5dc..3abb350f70 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 404ae8ea25..8786f7759c 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 25ce33b9bb..ed756faa99 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index e2b1ccca95..266133d35d 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 9be4f8726c..f269add714 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index 0b1f5a2fee..4af0ef96f8 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 2a6b44e568..5ded48406b 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 35776c8892..24949a9c4c 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.45 diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index 957e9895cd..1be93007c0 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 660fce4cfe..3d46a70a98 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index 5d5c5a7ab6..fb424a5900 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45-SNAPSHOT + 2.12.45 .. strata-report diff --git a/pom.xml b/pom.xml index 1b6b05e7b2..ea4eaf201a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.45-SNAPSHOT + 2.12.45 pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.45 From 4fb3073b6102bdcfae57eb3d9c04751a273ec1de Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Thu, 21 Nov 2024 16:53:21 +0000 Subject: [PATCH 20/29] [maven-release-plugin] prepare for next development iteration --- examples/pom.xml | 6 +----- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index aa82d26b2f..1a0efbe309 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.45 + 2.12.46-SNAPSHOT jar Strata-Examples Example code to demonstrate use of Strata @@ -272,8 +272,4 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> - - - v2.12.45 - diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 8727d3c33a..ec7131e6f7 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 3abb350f70..eb0037e282 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 8786f7759c..a88d0ce8b6 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index ed756faa99..50b754a2d2 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 266133d35d..5cb9f66566 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index f269add714..241510e6c3 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index 4af0ef96f8..f9dfa872b1 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index 5ded48406b..c2acab46bc 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 24949a9c4c..19fcf51967 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.45 + HEAD diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index 1be93007c0..c202432586 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 3d46a70a98..24563ea104 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index fb424a5900..c6842080ca 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.45 + 2.12.46-SNAPSHOT .. strata-report diff --git a/pom.xml b/pom.xml index ea4eaf201a..149e6ba089 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.45 + 2.12.46-SNAPSHOT pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.45 + HEAD From b2d5472510df39af1ed1596dd1a73d471dbbabc6 Mon Sep 17 00:00:00 2001 From: Andras Vig <93195277+Andras1022@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:29:17 +0100 Subject: [PATCH 21/29] =?UTF-8?q?Adding=20Dia=20Nacional=20de=20Zumbi=20e?= =?UTF-8?q?=20da=20Consci=C3=AAncia=20Negra=20to=20BRBD=20(#2685)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../basics/date/GlobalHolidayCalendars.java | 5 +++++ .../basics/date/GlobalHolidayCalendars.bin | Bin 178050 -> 178050 bytes .../date/GlobalHolidayCalendarsTest.java | 2 ++ 3 files changed, 7 insertions(+) diff --git a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java index e3394ab0f9..fec3db3d40 100644 --- a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java +++ b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java @@ -1209,6 +1209,7 @@ static ImmutableHolidayCalendar generateMexicoCity() { // http://www.planalto.gov.br/ccivil_03/leis/l0662.htm // http://www.planalto.gov.br/ccivil_03/Leis/L6802.htm // http://www.planalto.gov.br/ccivil_03/leis/2002/L10607.htm + // https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14759.htm static ImmutableHolidayCalendar generateBrazil() { // base law is from 1949, reworded in 2002 List holidays = new ArrayList<>(2000); @@ -1236,6 +1237,10 @@ static ImmutableHolidayCalendar generateBrazil() { holidays.add(date(year, 11, 2)); // republic holidays.add(date(year, 11, 15)); + // Dia Nacional de Zumbi e da Consciência Negra + if (year >= 2024) { + holidays.add(date(year, 11, 20)); + } // christmas holidays.add(date(year, 12, 25)); } diff --git a/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin b/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin index 5599a791a5c8ee428b52049b0ad8b59064fbcd2c..5aebea8dd34d645727fad9b7ea41ac9f79269dd4 100644 GIT binary patch delta 398 zcmY+AJr06E5QVdrR2+c|DE_W73a-Yebl$)l7z++SXe_1TwH(31#CAJcj^GWz8#p_z zMyi*2Z@!tyq_vaQPI4FiTu8W6;S1m}BPo&1#ZkQQG*ygqk3{0I;Fe?|u9Ud}XGxb$ z%M}4-KcFxikcA{AbjUL;m-%E$Bumb2OP;9|n{BRA%=K54#jsp8pbF3{=87?AddQ~T z=Z*N5Hqe-sGT-n>i`_=#$$VlGG#j3%O(kL1d?>ivf2=Y~zmLmzj8*1~G4zfE@FYnI TZ_kIc`fYYmk$9-6vp&56^Z0zo delta 296 zcmZ9HJxfAy7{xh_YS^A6Q9_IN=Dps3>HYf#c1sY1UX~yi-IDqWbsGc)*8>)~OW+bT zB)Ess9)e#W@B=i|BXXCvhx76~JpLl&FG6=OFkQwoxMttQ1?L&uvaYesPXkf*H13z$ zI%7HPaAYIQP7&SbWNh8@KUDahu#qBgt?X=gWxZn=NY*2R&XMA;F1* u7Jn@4bJLT^Na2Gm2X%fO2x@vPDDzNIof8SqmAr2#?6tiLUcJhizWD Date: Thu, 28 Nov 2024 10:57:56 +0000 Subject: [PATCH 22/29] [maven-release-plugin] prepare release v2.12.46 --- examples/pom.xml | 6 +++++- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 1a0efbe309..a117f1e9d8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.46-SNAPSHOT + 2.12.46 jar Strata-Examples Example code to demonstrate use of Strata @@ -272,4 +272,8 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> + + + v2.12.46 + diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index ec7131e6f7..2f8a53f1c7 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index eb0037e282..01648be46c 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index a88d0ce8b6..02c035dc35 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index 50b754a2d2..e834cc6be7 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index 5cb9f66566..f60bcc513d 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 241510e6c3..8534038aad 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index f9dfa872b1..a590c960d1 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index c2acab46bc..e8c42a3d0f 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 19fcf51967..205ff3aadc 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.46 diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index c202432586..bdb68db688 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 24563ea104..30a15174cf 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index c6842080ca..a98c9b6fd2 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46-SNAPSHOT + 2.12.46 .. strata-report diff --git a/pom.xml b/pom.xml index 149e6ba089..fd1db4d48b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.46-SNAPSHOT + 2.12.46 pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - HEAD + v2.12.46 From b402edf8795720d50eed04b90c43828b07c0d19b Mon Sep 17 00:00:00 2001 From: opengammacibot Date: Thu, 28 Nov 2024 10:57:58 +0000 Subject: [PATCH 23/29] [maven-release-plugin] prepare for next development iteration --- examples/pom.xml | 6 +----- modules/basics/pom.xml | 2 +- modules/calc/pom.xml | 2 +- modules/collect/pom.xml | 2 +- modules/data/pom.xml | 2 +- modules/loader/pom.xml | 2 +- modules/market/pom.xml | 2 +- modules/math/pom.xml | 2 +- modules/measure/pom.xml | 2 +- modules/pom.xml | 4 ++-- modules/pricer/pom.xml | 2 +- modules/product/pom.xml | 2 +- modules/report/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index a117f1e9d8..be40c67263 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-examples - 2.12.46 + 2.12.47-SNAPSHOT jar Strata-Examples Example code to demonstrate use of Strata @@ -272,8 +272,4 @@ OpenGamma Strata Examples OpenGamma Strata Examples]]> - - - v2.12.46 - diff --git a/modules/basics/pom.xml b/modules/basics/pom.xml index 2f8a53f1c7..f3abf744bc 100644 --- a/modules/basics/pom.xml +++ b/modules/basics/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-basics diff --git a/modules/calc/pom.xml b/modules/calc/pom.xml index 01648be46c..92febdc8ab 100644 --- a/modules/calc/pom.xml +++ b/modules/calc/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-calc diff --git a/modules/collect/pom.xml b/modules/collect/pom.xml index 02c035dc35..c34cc7dd05 100644 --- a/modules/collect/pom.xml +++ b/modules/collect/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-collect diff --git a/modules/data/pom.xml b/modules/data/pom.xml index e834cc6be7..c4dc4fa53f 100644 --- a/modules/data/pom.xml +++ b/modules/data/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-data diff --git a/modules/loader/pom.xml b/modules/loader/pom.xml index f60bcc513d..a4dbb890b5 100644 --- a/modules/loader/pom.xml +++ b/modules/loader/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-loader diff --git a/modules/market/pom.xml b/modules/market/pom.xml index 8534038aad..09db2689f5 100644 --- a/modules/market/pom.xml +++ b/modules/market/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-market diff --git a/modules/math/pom.xml b/modules/math/pom.xml index a590c960d1..36f04cb49b 100644 --- a/modules/math/pom.xml +++ b/modules/math/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-math diff --git a/modules/measure/pom.xml b/modules/measure/pom.xml index e8c42a3d0f..71210b4ead 100644 --- a/modules/measure/pom.xml +++ b/modules/measure/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-measure diff --git a/modules/pom.xml b/modules/pom.xml index 205ff3aadc..b9a4c33d3a 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT pom Strata-Parent OpenGamma Strata Parent @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.46 + HEAD diff --git a/modules/pricer/pom.xml b/modules/pricer/pom.xml index bdb68db688..df58e8a597 100644 --- a/modules/pricer/pom.xml +++ b/modules/pricer/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-pricer diff --git a/modules/product/pom.xml b/modules/product/pom.xml index 30a15174cf..4af4ec84bc 100644 --- a/modules/product/pom.xml +++ b/modules/product/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-product diff --git a/modules/report/pom.xml b/modules/report/pom.xml index a98c9b6fd2..2e9a14084c 100644 --- a/modules/report/pom.xml +++ b/modules/report/pom.xml @@ -5,7 +5,7 @@ com.opengamma.strata strata-parent - 2.12.46 + 2.12.47-SNAPSHOT .. strata-report diff --git a/pom.xml b/pom.xml index fd1db4d48b..5541a05794 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.opengamma.strata strata-root - 2.12.46 + 2.12.47-SNAPSHOT pom Strata-Root OpenGamma Strata root @@ -40,7 +40,7 @@ scm:git:https://github.com/OpenGamma/Strata.git scm:git:https://github.com/OpenGamma/Strata.git https://github.com/OpenGamma/Strata - v2.12.46 + HEAD From 45bed8bbe2d30d4201c893c6e3f248c17e086264 Mon Sep 17 00:00:00 2001 From: Brian Weller Date: Thu, 19 Dec 2024 10:42:55 +0000 Subject: [PATCH 24/29] Create OvernightOvernightSwapConventions (#2686) * Adding Overnight-Overnight swap conventions * Cleanup + Add Tests * PR cleanup * Finish PR feedback * Fix tests * Make constant public to fix ini loading --------- Co-authored-by: Brian Weller --- ...tableOvernightOvernightSwapConvention.java | 548 ++++++++++++++++++ .../OvernightOvernightSwapConvention.java | 211 +++++++ .../OvernightOvernightSwapConventions.java | 39 ++ .../StandardOvernightIborSwapConventions.java | 8 +- ...dardOvernightOvernightSwapConventions.java | 57 ++ .../base/OvernightOvernightSwapConvention.ini | 15 + .../OvernightOvernightSwapConventionTest.java | 208 +++++++ ...OvernightOvernightSwapConventionsTest.java | 141 +++++ 8 files changed, 1221 insertions(+), 6 deletions(-) create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/swap/type/ImmutableOvernightOvernightSwapConvention.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConvention.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventions.java create mode 100644 modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightOvernightSwapConventions.java create mode 100644 modules/product/src/main/resources/META-INF/com/opengamma/strata/config/base/OvernightOvernightSwapConvention.ini create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionTest.java create mode 100644 modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionsTest.java diff --git a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/ImmutableOvernightOvernightSwapConvention.java b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/ImmutableOvernightOvernightSwapConvention.java new file mode 100644 index 0000000000..df20a47ca7 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/ImmutableOvernightOvernightSwapConvention.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.joda.beans.Bean; +import org.joda.beans.ImmutableBean; +import org.joda.beans.JodaBeanUtils; +import org.joda.beans.MetaBean; +import org.joda.beans.MetaProperty; +import org.joda.beans.gen.BeanDefinition; +import org.joda.beans.gen.ImmutableValidator; +import org.joda.beans.gen.PropertyDefinition; +import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; +import org.joda.beans.impl.direct.DirectMetaBean; +import org.joda.beans.impl.direct.DirectMetaProperty; +import org.joda.beans.impl.direct.DirectMetaPropertyMap; + +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.common.BuySell; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.swap.Swap; +import com.opengamma.strata.product.swap.SwapLeg; +import com.opengamma.strata.product.swap.SwapTrade; + +/** + * A market convention for Overnight-Overnight swap trades. + *

+ * This defines the market convention for a Overnight-Overnight single currency swap. + * The convention is formed by combining two swap leg conventions in the same currency. + *

+ * The market price is for the difference (spread) between the values of the two legs. + * This convention has two legs, the "spread leg" and the "flat leg". The spread will be + * added to the "spread leg". + *

+ * The convention is defined by four key dates. + *

    + *
  • Trade date, the date that the trade is agreed + *
  • Spot date, the base for date calculations, typically 2 business days after the trade date + *
  • Start date, the date on which the interest calculation starts, often the same as the spot date + *
  • End date, the date on which the interest calculation ends, typically a number of years after the start date + *
+ */ +@BeanDefinition +public final class ImmutableOvernightOvernightSwapConvention + implements OvernightOvernightSwapConvention, ImmutableBean, Serializable { + + /** + * The convention name, such as 'USD-SOFR-3M-FED-FUND-3M'. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final String name; + /** + * The market convention of the floating leg that has the spread applied. + *

+ * The spread is the market price of the instrument. It is added to the observed interest rate. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final OvernightRateSwapLegConvention spreadLeg; + /** + * The market convention of the floating leg that does not have the spread applied. + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final OvernightRateSwapLegConvention flatLeg; + /** + * The offset of the spot value date from the trade date. + *

+ * The offset is applied to the trade date to find the start date. + * A typical value is "plus 2 business days". + */ + @PropertyDefinition(validate = "notNull", overrideGet = true) + private final DaysAdjustment spotDateOffset; + + //------------------------------------------------------------------------- + /** + * Obtains a convention based on the specified name, leg conventions and spot date offset. + *

+ * The two leg conventions must be in the same currency. + * + * @param name the unique name of the convention + * @param spreadLeg the market convention for the leg that the spread is added to + * @param flatLeg the market convention for the other leg, known as the flat leg + * @param spotDateOffset the offset of the spot value date from the trade date + * @return the convention + */ + public static ImmutableOvernightOvernightSwapConvention of( + String name, + OvernightRateSwapLegConvention spreadLeg, + OvernightRateSwapLegConvention flatLeg, + DaysAdjustment spotDateOffset) { + + return new ImmutableOvernightOvernightSwapConvention(name, spreadLeg, flatLeg, spotDateOffset); + } + + //------------------------------------------------------------------------- + @ImmutableValidator + private void validate() { + ArgChecker.isTrue(spreadLeg.getCurrency().equals(flatLeg.getCurrency()), "Conventions must have same currency"); + } + + //------------------------------------------------------------------------- + @Override + public SwapTrade toTrade( + TradeInfo tradeInfo, + LocalDate startDate, + LocalDate endDate, + BuySell buySell, + double notional, + double spread) { + + Optional tradeDate = tradeInfo.getTradeDate(); + if (tradeDate.isPresent()) { + ArgChecker.inOrderOrEqual(tradeDate.get(), startDate, "tradeDate", "startDate"); + } + SwapLeg leg1 = spreadLeg.toLeg(startDate, endDate, PayReceive.ofPay(buySell.isBuy()), notional, spread); + SwapLeg leg2 = flatLeg.toLeg(startDate, endDate, PayReceive.ofPay(buySell.isSell()), notional); + return SwapTrade.builder() + .info(tradeInfo) + .product(Swap.of(leg1, leg2)) + .build(); + } + + //------------------------------------------------------------------------- + @Override + public String toString() { + return getName(); + } + + //------------------------- AUTOGENERATED START ------------------------- + /** + * The meta-bean for {@code ImmutableOvernightOvernightSwapConvention}. + * @return the meta-bean, not null + */ + public static ImmutableOvernightOvernightSwapConvention.Meta meta() { + return ImmutableOvernightOvernightSwapConvention.Meta.INSTANCE; + } + + static { + MetaBean.register(ImmutableOvernightOvernightSwapConvention.Meta.INSTANCE); + } + + /** + * The serialization version id. + */ + private static final long serialVersionUID = 1L; + + /** + * Returns a builder used to create an instance of the bean. + * @return the builder, not null + */ + public static ImmutableOvernightOvernightSwapConvention.Builder builder() { + return new ImmutableOvernightOvernightSwapConvention.Builder(); + } + + private ImmutableOvernightOvernightSwapConvention( + String name, + OvernightRateSwapLegConvention spreadLeg, + OvernightRateSwapLegConvention flatLeg, + DaysAdjustment spotDateOffset) { + JodaBeanUtils.notNull(name, "name"); + JodaBeanUtils.notNull(spreadLeg, "spreadLeg"); + JodaBeanUtils.notNull(flatLeg, "flatLeg"); + JodaBeanUtils.notNull(spotDateOffset, "spotDateOffset"); + this.name = name; + this.spreadLeg = spreadLeg; + this.flatLeg = flatLeg; + this.spotDateOffset = spotDateOffset; + validate(); + } + + @Override + public ImmutableOvernightOvernightSwapConvention.Meta metaBean() { + return ImmutableOvernightOvernightSwapConvention.Meta.INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the convention name, such as 'USD-SOFR-3M-FED-FUND-3M'. + * @return the value of the property, not null + */ + @Override + public String getName() { + return name; + } + + //----------------------------------------------------------------------- + /** + * Gets the market convention of the floating leg that has the spread applied. + *

+ * The spread is the market price of the instrument. It is added to the observed interest rate. + * @return the value of the property, not null + */ + @Override + public OvernightRateSwapLegConvention getSpreadLeg() { + return spreadLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the market convention of the floating leg that does not have the spread applied. + * @return the value of the property, not null + */ + @Override + public OvernightRateSwapLegConvention getFlatLeg() { + return flatLeg; + } + + //----------------------------------------------------------------------- + /** + * Gets the offset of the spot value date from the trade date. + *

+ * The offset is applied to the trade date to find the start date. + * A typical value is "plus 2 business days". + * @return the value of the property, not null + */ + @Override + public DaysAdjustment getSpotDateOffset() { + return spotDateOffset; + } + + //----------------------------------------------------------------------- + /** + * Returns a builder that allows this bean to be mutated. + * @return the mutable builder, not null + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj.getClass() == this.getClass()) { + ImmutableOvernightOvernightSwapConvention other = (ImmutableOvernightOvernightSwapConvention) obj; + return JodaBeanUtils.equal(name, other.name) && + JodaBeanUtils.equal(spreadLeg, other.spreadLeg) && + JodaBeanUtils.equal(flatLeg, other.flatLeg) && + JodaBeanUtils.equal(spotDateOffset, other.spotDateOffset); + } + return false; + } + + @Override + public int hashCode() { + int hash = getClass().hashCode(); + hash = hash * 31 + JodaBeanUtils.hashCode(name); + hash = hash * 31 + JodaBeanUtils.hashCode(spreadLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(flatLeg); + hash = hash * 31 + JodaBeanUtils.hashCode(spotDateOffset); + return hash; + } + + //----------------------------------------------------------------------- + /** + * The meta-bean for {@code ImmutableOvernightOvernightSwapConvention}. + */ + public static final class Meta extends DirectMetaBean { + /** + * The singleton instance of the meta-bean. + */ + static final Meta INSTANCE = new Meta(); + + /** + * The meta-property for the {@code name} property. + */ + private final MetaProperty name = DirectMetaProperty.ofImmutable( + this, "name", ImmutableOvernightOvernightSwapConvention.class, String.class); + /** + * The meta-property for the {@code spreadLeg} property. + */ + private final MetaProperty spreadLeg = DirectMetaProperty.ofImmutable( + this, "spreadLeg", ImmutableOvernightOvernightSwapConvention.class, OvernightRateSwapLegConvention.class); + /** + * The meta-property for the {@code flatLeg} property. + */ + private final MetaProperty flatLeg = DirectMetaProperty.ofImmutable( + this, "flatLeg", ImmutableOvernightOvernightSwapConvention.class, OvernightRateSwapLegConvention.class); + /** + * The meta-property for the {@code spotDateOffset} property. + */ + private final MetaProperty spotDateOffset = DirectMetaProperty.ofImmutable( + this, "spotDateOffset", ImmutableOvernightOvernightSwapConvention.class, DaysAdjustment.class); + /** + * The meta-properties. + */ + private final Map> metaPropertyMap$ = new DirectMetaPropertyMap( + this, null, + "name", + "spreadLeg", + "flatLeg", + "spotDateOffset"); + + /** + * Restricted constructor. + */ + private Meta() { + } + + @Override + protected MetaProperty metaPropertyGet(String propertyName) { + switch (propertyName.hashCode()) { + case 3373707: // name + return name; + case 1302781851: // spreadLeg + return spreadLeg; + case -778843179: // flatLeg + return flatLeg; + case 746995843: // spotDateOffset + return spotDateOffset; + } + return super.metaPropertyGet(propertyName); + } + + @Override + public ImmutableOvernightOvernightSwapConvention.Builder builder() { + return new ImmutableOvernightOvernightSwapConvention.Builder(); + } + + @Override + public Class beanType() { + return ImmutableOvernightOvernightSwapConvention.class; + } + + @Override + public Map> metaPropertyMap() { + return metaPropertyMap$; + } + + //----------------------------------------------------------------------- + /** + * The meta-property for the {@code name} property. + * @return the meta-property, not null + */ + public MetaProperty name() { + return name; + } + + /** + * The meta-property for the {@code spreadLeg} property. + * @return the meta-property, not null + */ + public MetaProperty spreadLeg() { + return spreadLeg; + } + + /** + * The meta-property for the {@code flatLeg} property. + * @return the meta-property, not null + */ + public MetaProperty flatLeg() { + return flatLeg; + } + + /** + * The meta-property for the {@code spotDateOffset} property. + * @return the meta-property, not null + */ + public MetaProperty spotDateOffset() { + return spotDateOffset; + } + + //----------------------------------------------------------------------- + @Override + protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { + switch (propertyName.hashCode()) { + case 3373707: // name + return ((ImmutableOvernightOvernightSwapConvention) bean).getName(); + case 1302781851: // spreadLeg + return ((ImmutableOvernightOvernightSwapConvention) bean).getSpreadLeg(); + case -778843179: // flatLeg + return ((ImmutableOvernightOvernightSwapConvention) bean).getFlatLeg(); + case 746995843: // spotDateOffset + return ((ImmutableOvernightOvernightSwapConvention) bean).getSpotDateOffset(); + } + return super.propertyGet(bean, propertyName, quiet); + } + + @Override + protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { + metaProperty(propertyName); + if (quiet) { + return; + } + throw new UnsupportedOperationException("Property cannot be written: " + propertyName); + } + + } + + //----------------------------------------------------------------------- + /** + * The bean-builder for {@code ImmutableOvernightOvernightSwapConvention}. + */ + public static final class Builder extends DirectFieldsBeanBuilder { + + private String name; + private OvernightRateSwapLegConvention spreadLeg; + private OvernightRateSwapLegConvention flatLeg; + private DaysAdjustment spotDateOffset; + + /** + * Restricted constructor. + */ + private Builder() { + } + + /** + * Restricted copy constructor. + * @param beanToCopy the bean to copy from, not null + */ + private Builder(ImmutableOvernightOvernightSwapConvention beanToCopy) { + this.name = beanToCopy.getName(); + this.spreadLeg = beanToCopy.getSpreadLeg(); + this.flatLeg = beanToCopy.getFlatLeg(); + this.spotDateOffset = beanToCopy.getSpotDateOffset(); + } + + //----------------------------------------------------------------------- + @Override + public Object get(String propertyName) { + switch (propertyName.hashCode()) { + case 3373707: // name + return name; + case 1302781851: // spreadLeg + return spreadLeg; + case -778843179: // flatLeg + return flatLeg; + case 746995843: // spotDateOffset + return spotDateOffset; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + } + + @Override + public Builder set(String propertyName, Object newValue) { + switch (propertyName.hashCode()) { + case 3373707: // name + this.name = (String) newValue; + break; + case 1302781851: // spreadLeg + this.spreadLeg = (OvernightRateSwapLegConvention) newValue; + break; + case -778843179: // flatLeg + this.flatLeg = (OvernightRateSwapLegConvention) newValue; + break; + case 746995843: // spotDateOffset + this.spotDateOffset = (DaysAdjustment) newValue; + break; + default: + throw new NoSuchElementException("Unknown property: " + propertyName); + } + return this; + } + + @Override + public Builder set(MetaProperty property, Object value) { + super.set(property, value); + return this; + } + + @Override + public ImmutableOvernightOvernightSwapConvention build() { + return new ImmutableOvernightOvernightSwapConvention( + name, + spreadLeg, + flatLeg, + spotDateOffset); + } + + //----------------------------------------------------------------------- + /** + * Sets the convention name, such as 'USD-SOFR-3M-FED-FUND-3M'. + * @param name the new value, not null + * @return this, for chaining, not null + */ + public Builder name(String name) { + JodaBeanUtils.notNull(name, "name"); + this.name = name; + return this; + } + + /** + * Sets the market convention of the floating leg that has the spread applied. + *

+ * The spread is the market price of the instrument. It is added to the observed interest rate. + * @param spreadLeg the new value, not null + * @return this, for chaining, not null + */ + public Builder spreadLeg(OvernightRateSwapLegConvention spreadLeg) { + JodaBeanUtils.notNull(spreadLeg, "spreadLeg"); + this.spreadLeg = spreadLeg; + return this; + } + + /** + * Sets the market convention of the floating leg that does not have the spread applied. + * @param flatLeg the new value, not null + * @return this, for chaining, not null + */ + public Builder flatLeg(OvernightRateSwapLegConvention flatLeg) { + JodaBeanUtils.notNull(flatLeg, "flatLeg"); + this.flatLeg = flatLeg; + return this; + } + + /** + * Sets the offset of the spot value date from the trade date. + *

+ * The offset is applied to the trade date to find the start date. + * A typical value is "plus 2 business days". + * @param spotDateOffset the new value, not null + * @return this, for chaining, not null + */ + public Builder spotDateOffset(DaysAdjustment spotDateOffset) { + JodaBeanUtils.notNull(spotDateOffset, "spotDateOffset"); + this.spotDateOffset = spotDateOffset; + return this; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(160); + buf.append("ImmutableOvernightOvernightSwapConvention.Builder{"); + buf.append("name").append('=').append(JodaBeanUtils.toString(name)).append(',').append(' '); + buf.append("spreadLeg").append('=').append(JodaBeanUtils.toString(spreadLeg)).append(',').append(' '); + buf.append("flatLeg").append('=').append(JodaBeanUtils.toString(flatLeg)).append(',').append(' '); + buf.append("spotDateOffset").append('=').append(JodaBeanUtils.toString(spotDateOffset)); + buf.append('}'); + return buf.toString(); + } + + } + + //-------------------------- AUTOGENERATED END -------------------------- +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConvention.java b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConvention.java new file mode 100644 index 0000000000..fc33ae301b --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConvention.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import java.time.LocalDate; +import java.time.Period; + +import org.joda.convert.FromString; +import org.joda.convert.ToString; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.ReferenceDataNotFoundException; +import com.opengamma.strata.basics.date.Tenor; +import com.opengamma.strata.collect.ArgChecker; +import com.opengamma.strata.collect.named.ExtendedEnum; +import com.opengamma.strata.collect.named.Named; +import com.opengamma.strata.product.TradeInfo; +import com.opengamma.strata.product.common.BuySell; +import com.opengamma.strata.product.swap.SwapTrade; + +/** + * A market convention for Overnight-Overnight swap trades. + *

+ * This defines the market convention for an Overnight-Overnight single currency swap. + * The convention is formed by combining two swap leg conventions in the same currency. + *

+ * To manually create a convention, see {@link ImmutableOvernightOvernightSwapConvention}. + * To register a specific convention, see {@code OvernightOvernightSwapConvention.ini}. + */ +public interface OvernightOvernightSwapConvention extends SingleCurrencySwapConvention, Named { + + /** + * Obtains an instance from the specified unique name. + * + * @param uniqueName the unique name + * @return the convention + * @throws IllegalArgumentException if the name is not known + */ + @FromString + public static OvernightOvernightSwapConvention of(String uniqueName) { + ArgChecker.notNull(uniqueName, "uniqueName"); + return extendedEnum().lookup(uniqueName); + } + + /** + * Gets the extended enum helper. + *

+ * This helper allows instances of the convention to be looked up. + * It also provides the complete set of available instances. + * + * @return the extended enum helper + */ + public static ExtendedEnum extendedEnum() { + return OvernightOvernightSwapConventions.ENUM_LOOKUP; + } + + //----------------------------------------------------------------------- + /** + * Gets the market convention of the spread leg. + * + * @return the spread leg convention + */ + public abstract OvernightRateSwapLegConvention getSpreadLeg(); + + /** + * Gets the market convention of the flat leg. + * + * @return the flat leg convention + */ + public abstract OvernightRateSwapLegConvention getFlatLeg(); + + //------------------------------------------------------------------------- + /** + * Creates a spot-starting trade based on this convention. + *

+ * This returns a trade based on the specified tenor. For example, a tenor + * of 5 years creates a swap starting on the spot date and maturing 5 years later. + *

+ * The notional is unsigned, with buy/sell determining the direction of the trade. + * If buying the swap, the rate of the flat leg is received from the counterparty, + * with the rate of the spread leg being paid. If selling the swap, the opposite occurs. + * + * @param tradeDate the date of the trade + * @param tenor the tenor of the swap + * @param buySell the buy/sell flag + * @param notional the notional amount + * @param spread the spread, typically derived from the market + * @param refData the reference data, used to resolve the trade dates + * @return the trade + * @throws ReferenceDataNotFoundException if an identifier cannot be resolved in the reference data + */ + @Override + public default SwapTrade createTrade( + LocalDate tradeDate, + Tenor tenor, + BuySell buySell, + double notional, + double spread, + ReferenceData refData) { + + // override for Javadoc + return SingleCurrencySwapConvention.super.createTrade(tradeDate, tenor, buySell, notional, spread, refData); + } + + /** + * Creates a forward-starting trade based on this convention. + *

+ * This returns a trade based on the specified period and tenor. For example, a period of + * 3 months and a tenor of 5 years creates a swap starting three months after the spot date + * and maturing 5 years later. + *

+ * The notional is unsigned, with buy/sell determining the direction of the trade. + * If buying the swap, the rate of the flat leg is received from the counterparty, + * with the rate of the spread leg being paid. If selling the swap, the opposite occurs. + * + * @param tradeDate the date of the trade + * @param periodToStart the period between the spot date and the start date + * @param tenor the tenor of the swap + * @param buySell the buy/sell flag + * @param notional the notional amount + * @param spread the spread, typically derived from the market + * @param refData the reference data, used to resolve the trade dates + * @return the trade + * @throws ReferenceDataNotFoundException if an identifier cannot be resolved in the reference data + */ + @Override + public default SwapTrade createTrade( + LocalDate tradeDate, + Period periodToStart, + Tenor tenor, + BuySell buySell, + double notional, + double spread, + ReferenceData refData) { + + // override for Javadoc + return SingleCurrencySwapConvention.super.createTrade(tradeDate, periodToStart, tenor, buySell, notional, spread, refData); + } + + /** + * Creates a trade based on this convention. + *

+ * This returns a trade based on the specified dates. + *

+ * The notional is unsigned, with buy/sell determining the direction of the trade. + * If buying the swap, the rate of the flat leg is received from the counterparty, + * with the rate of the spread leg being paid. If selling the swap, the opposite occurs. + * + * @param tradeDate the date of the trade + * @param startDate the start date + * @param endDate the end date + * @param buySell the buy/sell flag + * @param notional the notional amount + * @param spread the spread, typically derived from the market + * @return the trade + */ + @Override + public default SwapTrade toTrade( + LocalDate tradeDate, + LocalDate startDate, + LocalDate endDate, + BuySell buySell, + double notional, + double spread) { + + // override for Javadoc + return SingleCurrencySwapConvention.super.toTrade(tradeDate, startDate, endDate, buySell, notional, spread); + } + + /** + * Creates a trade based on this convention. + *

+ * This returns a trade based on the specified dates. + *

+ * The notional is unsigned, with buy/sell determining the direction of the trade. + * If buying the swap, the rate of the flat leg is received from the counterparty, + * with the rate of the spread leg being paid. If selling the swap, the opposite occurs. + * + * @param tradeInfo additional information about the trade + * @param startDate the start date + * @param endDate the end date + * @param buySell the buy/sell flag + * @param notional the notional amount + * @param spread the spread, typically derived from the market + * @return the trade + */ + @Override + public abstract SwapTrade toTrade( + TradeInfo tradeInfo, + LocalDate startDate, + LocalDate endDate, + BuySell buySell, + double notional, + double spread); + + //------------------------------------------------------------------------- + /** + * Gets the name that uniquely identifies this convention. + *

+ * This name is used in serialization and can be parsed using {@link #of(String)}. + * + * @return the unique name + */ + @ToString + @Override + public abstract String getName(); + +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventions.java b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventions.java new file mode 100644 index 0000000000..726ff0508c --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventions.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import com.opengamma.strata.collect.named.ExtendedEnum; + +/** + * Market standard Overnight-Overnight swap conventions. + */ +public final class OvernightOvernightSwapConventions { + + /** + * The extended enum lookup from name to instance. + */ + static final ExtendedEnum ENUM_LOOKUP = + ExtendedEnum.of(OvernightOvernightSwapConvention.class); + + //------------------------------------------------------------------------- + /** + * The 'USD-SOFR-3M-FED-FUND-3M' swap convention. + *

+ * USD SOFR v USD Fed Fund 3M swap. + *

+ * The spot date offset is 2 days. + */ + public static final OvernightOvernightSwapConvention USD_SOFR_3M_FED_FUND_3M = + OvernightOvernightSwapConvention.of(StandardOvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M.getName()); + + //------------------------------------------------------------------------- + /** + * Restricted constructor. + */ + private OvernightOvernightSwapConventions() { + } + +} diff --git a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightIborSwapConventions.java b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightIborSwapConventions.java index cb2ccc3a70..c1aaa032c6 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightIborSwapConventions.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightIborSwapConventions.java @@ -5,8 +5,6 @@ */ package com.opengamma.strata.product.swap.type; -import static com.opengamma.strata.basics.date.DayCounts.ACT_360; -import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; import static com.opengamma.strata.basics.index.IborIndices.GBP_LIBOR_3M; import static com.opengamma.strata.basics.index.IborIndices.USD_LIBOR_3M; import static com.opengamma.strata.basics.index.OvernightIndices.GBP_SONIA; @@ -16,7 +14,6 @@ import static com.opengamma.strata.product.swap.OvernightAccrualMethod.AVERAGED; import static com.opengamma.strata.product.swap.OvernightAccrualMethod.COMPOUNDED; -import com.opengamma.strata.basics.date.DayCount; import com.opengamma.strata.basics.date.DaysAdjustment; import com.opengamma.strata.basics.date.HolidayCalendarId; import com.opengamma.strata.basics.index.IborIndex; @@ -39,7 +36,7 @@ final class StandardOvernightIborSwapConventions { * The spot date offset is 2 days and the cut-off period is 2 days. */ public static final OvernightIborSwapConvention USD_FED_FUND_AA_LIBOR_3M = - makeConvention("USD-FED-FUND-AA-LIBOR-3M", USD_FED_FUND, USD_LIBOR_3M, ACT_360, P3M, 0, 2, AVERAGED); + makeConvention("USD-FED-FUND-AA-LIBOR-3M", USD_FED_FUND, USD_LIBOR_3M, P3M, 0, 2, AVERAGED); /** * GBP Sonia compounded 1Y v LIBOR 3M . @@ -48,7 +45,7 @@ final class StandardOvernightIborSwapConventions { * The spot date offset is 0 days and payment offset is 0 days. */ public static final OvernightIborSwapConvention GBP_SONIA_OIS_1Y_LIBOR_3M = - makeConvention("GBP-SONIA-OIS-1Y-LIBOR-3M", GBP_SONIA, GBP_LIBOR_3M, ACT_365F, P12M, 0, 0, COMPOUNDED); + makeConvention("GBP-SONIA-OIS-1Y-LIBOR-3M", GBP_SONIA, GBP_LIBOR_3M, P12M, 0, 0, COMPOUNDED); //------------------------------------------------------------------------- // build conventions @@ -56,7 +53,6 @@ private static OvernightIborSwapConvention makeConvention( String name, OvernightIndex onIndex, IborIndex iborIndex, - DayCount dayCount, Frequency frequency, int paymentLag, int cutOffDays, diff --git a/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightOvernightSwapConventions.java b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightOvernightSwapConventions.java new file mode 100644 index 0000000000..2e459de8e6 --- /dev/null +++ b/modules/product/src/main/java/com/opengamma/strata/product/swap/type/StandardOvernightOvernightSwapConventions.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import static com.opengamma.strata.basics.index.OvernightIndices.USD_FED_FUND; +import static com.opengamma.strata.basics.index.OvernightIndices.USD_SOFR; +import static com.opengamma.strata.basics.schedule.Frequency.P3M; + +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.date.HolidayCalendarId; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.basics.schedule.Frequency; + +/** + * Market standard Overnight-Overnight swap conventions. + */ +final class StandardOvernightOvernightSwapConventions { + + /** + * USD SOFR vs USD Fed Fund 3M swap. + *

+ * The spot date offset is 2 days. + */ + public static final OvernightOvernightSwapConvention USD_SOFR_3M_FED_FUND_3M = + makeConvention("USD-SOFR-3M-FED-FUND-3M", USD_SOFR, USD_FED_FUND, P3M, 2, 2); + + //------------------------------------------------------------------------- + // build conventions + private static OvernightOvernightSwapConvention makeConvention( + String name, + OvernightIndex spreadIndex, + OvernightIndex flatIndex, + Frequency frequency, + int paymentLag, + int spotLag) { + + HolidayCalendarId calendar = spreadIndex.getFixingCalendar(); + DaysAdjustment spotDateOffset = DaysAdjustment.ofBusinessDays(spotLag, calendar); + return ImmutableOvernightOvernightSwapConvention.of( + name, + OvernightRateSwapLegConvention.of(spreadIndex, frequency, paymentLag), + OvernightRateSwapLegConvention.of(flatIndex, frequency, paymentLag), + spotDateOffset); + } + + //------------------------------------------------------------------------- + + /** + * Restricted constructor. + */ + private StandardOvernightOvernightSwapConventions() { + } + +} diff --git a/modules/product/src/main/resources/META-INF/com/opengamma/strata/config/base/OvernightOvernightSwapConvention.ini b/modules/product/src/main/resources/META-INF/com/opengamma/strata/config/base/OvernightOvernightSwapConvention.ini new file mode 100644 index 0000000000..81ccdf22e9 --- /dev/null +++ b/modules/product/src/main/resources/META-INF/com/opengamma/strata/config/base/OvernightOvernightSwapConvention.ini @@ -0,0 +1,15 @@ +# OvernightOvernightSwapConvention configuration + +# The providers are the classes that define the enum +# The key is of the form 'provider.full.class.name' +# The value is either +# 'constants', the public static final constants from the class +# 'lookup', the class implements NamedLookup with a no-args constructor +# 'instance', the class has a static field named INSTANCE that is of type NamedLookup +[providers] +com.opengamma.strata.product.swap.type.StandardOvernightOvernightSwapConventions = constants + +# The set of alternate names +# The key is the alternate name +# The value is the standard name (loaded by a provider) +[alternates] diff --git a/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionTest.java b/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionTest.java new file mode 100644 index 0000000000..a839ee692b --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import static com.opengamma.strata.basics.date.HolidayCalendarIds.GBLO; +import static com.opengamma.strata.basics.date.Tenor.TENOR_10Y; +import static com.opengamma.strata.basics.index.OvernightIndices.USD_FED_FUND; +import static com.opengamma.strata.basics.index.OvernightIndices.USD_SOFR; +import static com.opengamma.strata.basics.schedule.Frequency.P3M; +import static com.opengamma.strata.collect.TestHelper.assertSerialization; +import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; +import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; +import static com.opengamma.strata.collect.TestHelper.date; +import static com.opengamma.strata.product.common.BuySell.BUY; +import static com.opengamma.strata.product.common.PayReceive.PAY; +import static com.opengamma.strata.product.common.PayReceive.RECEIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import java.time.Period; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.google.common.collect.ImmutableMap; +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.DaysAdjustment; +import com.opengamma.strata.basics.index.OvernightIndices; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.basics.schedule.StubConvention; +import com.opengamma.strata.product.common.BuySell; +import com.opengamma.strata.product.swap.Swap; +import com.opengamma.strata.product.swap.SwapTrade; + +/** + * Test {@link OvernightOvernightSwapConvention}. + */ +public class OvernightOvernightSwapConventionTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + private static final double NOTIONAL_2M = 2_000_000d; + private static final DaysAdjustment PLUS_ONE_DAY = DaysAdjustment.ofBusinessDays(1, GBLO); + private static final DaysAdjustment PLUS_TWO_DAYS = DaysAdjustment.ofBusinessDays(2, GBLO); + + private static final String NAME = "USD-SOFR-FF"; + + private static final OvernightRateSwapLegConvention SOFR_LEG = + OvernightRateSwapLegConvention.builder() + .index(USD_SOFR) + .accrualFrequency(Frequency.P3M) + .paymentFrequency(Frequency.P3M) + .stubConvention(StubConvention.SMART_INITIAL) + .rateCutOffDays(2) + .build(); + + private static final OvernightRateSwapLegConvention FFUND_LEG = + OvernightRateSwapLegConvention.builder() + .index(USD_FED_FUND) + .accrualFrequency(Frequency.P3M) + .paymentFrequency(Frequency.P3M) + .stubConvention(StubConvention.SMART_INITIAL) + .rateCutOffDays(2) + .build(); + + //------------------------------------------------------------------------- + @Test + public void test_of() { + OvernightOvernightSwapConvention test = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + assertThat(test.getName()).isEqualTo(NAME); + assertThat(test.getSpreadLeg()).isEqualTo(SOFR_LEG); + assertThat(test.getFlatLeg()).isEqualTo(FFUND_LEG); + assertThat(test.getSpotDateOffset()).isEqualTo(PLUS_TWO_DAYS); + } + + @Test + public void test_builder() { + ImmutableOvernightOvernightSwapConvention test = ImmutableOvernightOvernightSwapConvention.builder() + .name(NAME) + .spreadLeg(SOFR_LEG) + .flatLeg(FFUND_LEG) + .spotDateOffset(PLUS_ONE_DAY) + .build(); + assertThat(test.getName()).isEqualTo(NAME); + assertThat(test.getSpreadLeg()).isEqualTo(SOFR_LEG); + assertThat(test.getFlatLeg()).isEqualTo(FFUND_LEG); + assertThat(test.getSpotDateOffset()).isEqualTo(PLUS_ONE_DAY); + } + + //------------------------------------------------------------------------- + @Test + public void test_toTrade_tenor() { + OvernightOvernightSwapConvention base = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + LocalDate tradeDate = LocalDate.of(2015, 5, 5); + LocalDate startDate = date(2015, 5, 7); + LocalDate endDate = date(2025, 5, 7); + SwapTrade test = base.createTrade(tradeDate, TENOR_10Y, BUY, NOTIONAL_2M, 0.25d, REF_DATA); + Swap expected = Swap.of( + SOFR_LEG.toLeg(startDate, endDate, PAY, NOTIONAL_2M, 0.25d), + FFUND_LEG.toLeg(startDate, endDate, RECEIVE, NOTIONAL_2M)); + assertThat(test.getInfo().getTradeDate()).isEqualTo(Optional.of(tradeDate)); + assertThat(test.getProduct()).isEqualTo(expected); + } + + @Test + public void test_toTrade_periodTenor() { + OvernightOvernightSwapConvention base = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + LocalDate tradeDate = LocalDate.of(2015, 5, 5); + LocalDate startDate = date(2015, 8, 7); + LocalDate endDate = date(2025, 8, 7); + SwapTrade test = base.createTrade(tradeDate, Period.ofMonths(3), TENOR_10Y, BuySell.SELL, NOTIONAL_2M, 0.25d, REF_DATA); + Swap expected = Swap.of( + SOFR_LEG.toLeg(startDate, endDate, RECEIVE, NOTIONAL_2M, 0.25d), + FFUND_LEG.toLeg(startDate, endDate, PAY, NOTIONAL_2M)); + assertThat(test.getInfo().getTradeDate()).isEqualTo(Optional.of(tradeDate)); + assertThat(test.getProduct()).isEqualTo(expected); + } + + @Test + public void test_toTrade_dates() { + OvernightOvernightSwapConvention base = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + LocalDate tradeDate = LocalDate.of(2015, 5, 5); + LocalDate startDate = date(2015, 8, 5); + LocalDate endDate = date(2015, 11, 5); + SwapTrade test = base.toTrade(tradeDate, startDate, endDate, BUY, NOTIONAL_2M, 0.25d); + Swap expected = Swap.of( + SOFR_LEG.toLeg(startDate, endDate, PAY, NOTIONAL_2M, 0.25d), + FFUND_LEG.toLeg(startDate, endDate, RECEIVE, NOTIONAL_2M)); + assertThat(test.getInfo().getTradeDate()).isEqualTo(Optional.of(tradeDate)); + assertThat(test.getProduct()).isEqualTo(expected); + } + + //------------------------------------------------------------------------- + public static Object[][] data_name() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, "USD-SOFR-3M-FED-FUND-3M"}, + }; + } + + @ParameterizedTest + @MethodSource("data_name") + public void test_name(OvernightOvernightSwapConvention convention, String name) { + assertThat(convention.getName()).isEqualTo(name); + } + + @ParameterizedTest + @MethodSource("data_name") + public void test_toString(OvernightOvernightSwapConvention convention, String name) { + assertThat(convention.toString()).isEqualTo(name); + } + + @ParameterizedTest + @MethodSource("data_name") + public void test_of_lookup(OvernightOvernightSwapConvention convention, String name) { + assertThat(OvernightOvernightSwapConvention.of(name)).isEqualTo(convention); + } + + @ParameterizedTest + @MethodSource("data_name") + public void test_extendedEnum(OvernightOvernightSwapConvention convention, String name) { + OvernightOvernightSwapConvention.of(name); // ensures map is populated + ImmutableMap map = OvernightOvernightSwapConvention.extendedEnum().lookupAll(); + assertThat(map.get(name)).isEqualTo(convention); + } + + @Test + public void test_of_lookup_notFound() { + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightOvernightSwapConvention.of("Rubbish")); + } + + @Test + public void test_of_lookup_null() { + assertThatIllegalArgumentException() + .isThrownBy(() -> OvernightOvernightSwapConvention.of(null)); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + ImmutableOvernightOvernightSwapConvention test = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + coverImmutableBean(test); + ImmutableOvernightOvernightSwapConvention test2 = ImmutableOvernightOvernightSwapConvention.of( + "EUR-EONIA-ESTR", + OvernightRateSwapLegConvention.of(OvernightIndices.EUR_EONIA, P3M, 2), + OvernightRateSwapLegConvention.of(OvernightIndices.EUR_EONIA, P3M, 2), + PLUS_TWO_DAYS); + coverBeanEquals(test, test2); + } + + @Test + public void test_serialization() { + OvernightOvernightSwapConvention test = + ImmutableOvernightOvernightSwapConvention.of(NAME, SOFR_LEG, FFUND_LEG, PLUS_TWO_DAYS); + assertSerialization(test); + } + +} diff --git a/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionsTest.java b/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionsTest.java new file mode 100644 index 0000000000..6eb75f9135 --- /dev/null +++ b/modules/product/src/test/java/com/opengamma/strata/product/swap/type/OvernightOvernightSwapConventionsTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024 - present by OpenGamma Inc. and the OpenGamma group of companies + * + * Please see distribution for license. + */ +package com.opengamma.strata.product.swap.type; + +import static com.opengamma.strata.collect.TestHelper.coverPrivateConstructor; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.opengamma.strata.basics.ReferenceData; +import com.opengamma.strata.basics.date.BusinessDayConvention; +import com.opengamma.strata.basics.date.BusinessDayConventions; +import com.opengamma.strata.basics.date.DayCount; +import com.opengamma.strata.basics.date.DayCounts; +import com.opengamma.strata.basics.date.Tenor; +import com.opengamma.strata.basics.index.OvernightIndex; +import com.opengamma.strata.basics.index.OvernightIndices; +import com.opengamma.strata.basics.schedule.Frequency; +import com.opengamma.strata.product.common.BuySell; +import com.opengamma.strata.product.common.PayReceive; +import com.opengamma.strata.product.swap.ResolvedSwap; +import com.opengamma.strata.product.swap.SwapTrade; + +/** + * Test {@link OvernightOvernightSwapConventions}. + */ +public class OvernightOvernightSwapConventionsTest { + + private static final ReferenceData REF_DATA = ReferenceData.standard(); + + public static Object[][] data_spot_lag() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, 2}, + }; + } + + @ParameterizedTest + @MethodSource("data_spot_lag") + public void test_spot_lag(OvernightOvernightSwapConvention convention, int lag) { + assertThat(convention.getSpotDateOffset().getDays()).isEqualTo(lag); + } + + //------------------------------------------------------------------------- + public static Object[][] data_period() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, Frequency.P3M} + }; + } + + @ParameterizedTest + @MethodSource("data_period") + public void test_accrualPeriod_on(OvernightOvernightSwapConvention convention, Frequency frequency) { + assertThat(convention.getSpreadLeg().getAccrualFrequency()).isEqualTo(frequency); + assertThat(convention.getFlatLeg().getAccrualFrequency()).isEqualTo(frequency); + } + + @ParameterizedTest + @MethodSource("data_period") + public void test_paymentPeriod_on(OvernightOvernightSwapConvention convention, Frequency frequency) { + assertThat(convention.getSpreadLeg().getPaymentFrequency()).isEqualTo(frequency); + assertThat(convention.getFlatLeg().getPaymentFrequency()).isEqualTo(frequency); + } + + //------------------------------------------------------------------------- + public static Object[][] data_day_count() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, DayCounts.ACT_360}, + }; + } + + @ParameterizedTest + @MethodSource("data_day_count") + public void test_day_count(OvernightOvernightSwapConvention convention, DayCount dayCount) { + assertThat(convention.getSpreadLeg().getDayCount()).isEqualTo(dayCount); + assertThat(convention.getFlatLeg().getDayCount()).isEqualTo(dayCount); + } + + //------------------------------------------------------------------------- + public static Object[][] data_float_index() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, OvernightIndices.USD_SOFR, OvernightIndices.USD_FED_FUND}, + }; + } + + @ParameterizedTest + @MethodSource("data_float_index") + public void test_float_leg( + OvernightOvernightSwapConvention convention, + OvernightIndex spreadLegIndex, + OvernightIndex flatLegIndex) { + + assertThat(convention.getSpreadLeg().getIndex()).isEqualTo(spreadLegIndex); + assertThat(convention.getFlatLeg().getIndex()).isEqualTo(flatLegIndex); + } + + //------------------------------------------------------------------------- + public static Object[][] data_day_convention() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, BusinessDayConventions.MODIFIED_FOLLOWING} + }; + } + + @ParameterizedTest + @MethodSource("data_day_convention") + public void test_day_convention(OvernightOvernightSwapConvention convention, BusinessDayConvention dayConvention) { + assertThat(convention.getSpreadLeg().getAccrualBusinessDayAdjustment().getConvention()).isEqualTo(dayConvention); + assertThat(convention.getFlatLeg().getAccrualBusinessDayAdjustment().getConvention()).isEqualTo(dayConvention); + } + + //------------------------------------------------------------------------- + public static Object[][] data_stub_on() { + return new Object[][] { + {OvernightOvernightSwapConventions.USD_SOFR_3M_FED_FUND_3M, Tenor.TENOR_4M} + }; + } + + @ParameterizedTest + @MethodSource("data_stub_on") + public void test_stub_overnight(OvernightOvernightSwapConvention convention, Tenor tenor) { + LocalDate tradeDate = LocalDate.of(2015, 10, 20); + SwapTrade swap = convention.createTrade(tradeDate, tenor, BuySell.BUY, 1, 0.01, REF_DATA); + ResolvedSwap swapResolved = swap.getProduct().resolve(REF_DATA); + LocalDate endDate = swapResolved.getLeg(PayReceive.PAY).get().getEndDate(); + assertThat(endDate.isAfter(tradeDate.plus(tenor).minusDays(7))).isTrue(); + assertThat(endDate.isBefore(tradeDate.plus(tenor).plusDays(7))).isTrue(); + } + + //------------------------------------------------------------------------- + @Test + public void coverage() { + coverPrivateConstructor(OvernightOvernightSwapConventions.class); + coverPrivateConstructor(StandardOvernightOvernightSwapConventions.class); + } +} From 4fef1c387a0862384d2ecae8600e8c3c530d9257 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Sun, 5 Jan 2025 11:46:59 +0000 Subject: [PATCH 25/29] Add US National Day of Mourning (#2692) * Holiday for President Jimmy Carter * Only appears to apply to NYSE * https://www.sifma.org/resources/unscheduled-close-market-matrix/ * Fixes #2691 --- .../basics/date/GlobalHolidayCalendars.java | 5 +++-- .../basics/date/GlobalHolidayCalendars.bin | Bin 178050 -> 178050 bytes 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java index fec3db3d40..ee79eba8cf 100644 --- a/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java +++ b/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java @@ -370,7 +370,7 @@ private static void usCommon( } // generate USGS - // http://www.sifma.org/services/holiday-schedule/ + // https://www.sifma.org/resources/general/holiday-schedule/ static ImmutableHolidayCalendar generateUsGovtSecurities() { List holidays = new ArrayList<>(2000); for (int year = 1950; year <= 2099; year++) { @@ -404,7 +404,7 @@ static ImmutableHolidayCalendar generateUsNewYork() { //------------------------------------------------------------------------- // generate NYFD - // http://www.ny.frb.org/aboutthefed/holiday_schedule.html + // https://www.newyorkfed.org/aboutthefed/holiday_schedule.html static ImmutableHolidayCalendar generateNewYorkFed() { List holidays = new ArrayList<>(2000); for (int year = 1950; year <= 2099; year++) { @@ -496,6 +496,7 @@ static ImmutableHolidayCalendar generateNewYorkStockExchange() { holidays.add(date(2007, 1, 2)); // Death of Gerald Ford holidays.add(date(2012, 10, 30)); // Hurricane Sandy holidays.add(date(2018, 12, 5)); // Death of George H.W. Bush + holidays.add(date(2025, 1, 9)); // Death of Jimmy Carter removeSatSun(holidays); return ImmutableHolidayCalendar.of(HolidayCalendarIds.NYSE, holidays, SATURDAY, SUNDAY); } diff --git a/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin b/modules/basics/src/main/resources/com/opengamma/strata/basics/date/GlobalHolidayCalendars.bin index 5aebea8dd34d645727fad9b7ea41ac9f79269dd4..5b85139fad061df5e0b456b571c704ff1d02deea 100644 GIT binary patch delta 24 icmV+z0O$XL?+SwN3b3^10rHbEk|TrI=C|190jkiY{SDp# delta 22 ecmZpA&(-vvYr~djlT*IfG=F%u{lhcHNf!W?%nh*s From d2cd86a7dbfa77e3cedeb4cdc090a5ee17dab824 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 11:57:32 +0000 Subject: [PATCH 26/29] Bump com.google.guava:guava from 32.1.3-jre to 33.4.0-jre (#2688) Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.1.3-jre to 33.4.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- modules/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pom.xml b/modules/pom.xml index b9a4c33d3a..c6272f05a5 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -765,7 +765,7 @@ 9.5 3.25.3 1.6.Final - 32.1.3-jre + 33.4.0-jre 26.0-jre 1.78 2.2.3 From ac1fc5ad9afaacf1dcdcb1b783b229d055858b3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:04:50 +0000 Subject: [PATCH 27/29] Bump org.junit:junit-bom from 5.11.2 to 5.11.4 (#2690) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.11.2 to 5.11.4. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.4) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- modules/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pom.xml b/modules/pom.xml index c6272f05a5..8cd1cd09e2 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -771,7 +771,7 @@ 2.2.3 2.11.0 ${joda-beans.version} - 5.11.2 + 5.11.4 4.9.0 1.7.36 From 440dd045a062d0d1546e7450f9aca3fc802627b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 12:11:46 +0000 Subject: [PATCH 28/29] Bump org.assertj:assertj-core from 3.25.3 to 3.27.0 (#2689) Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.25.3 to 3.27.0. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.25.3...assertj-build-3.27.0) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- modules/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pom.xml b/modules/pom.xml index 8cd1cd09e2..f55aad701a 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -763,7 +763,7 @@ ${project.basedir}/.. 9.5 - 3.25.3 + 3.27.0 1.6.Final 33.4.0-jre 26.0-jre From 3168e0f378dbdf713a4ef5694103ff4740174039 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Sun, 5 Jan 2025 19:49:32 +0000 Subject: [PATCH 29/29] * Adjust new property name * Provide default value for new property * Adjust code to pull out events per year --- ...scountingFixedCouponBondProductPricer.java | 32 +++---- ...ntingFixedCouponBondProductPricerTest.java | 1 + .../strata/product/bond/FixedCouponBond.java | 2 +- .../bond/FixedCouponBondPaymentPeriod.java | 85 ++++++++++--------- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java index 440f5887ce..31d3dc2084 100644 --- a/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java +++ b/modules/pricer/src/main/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricer.java @@ -555,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))) { @@ -593,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)); } } @@ -626,8 +626,8 @@ private double dirtyPriceFromYieldStandard( double yield) { List periodicPayments = bond.getPeriodicPayments(); - int eventsPerYear = bond.getFrequency().eventsPerYear(); - double factorOnPeriod = 1 + yield / ((double) eventsPerYear); + double eventsPerYear = bond.getFrequency().eventsPerYear(); + double factorOnPeriod = 1 + yield / eventsPerYear; double fixedRate = bond.getFixedRate(); double pvAtFirstCoupon = 0; double factor = 1; @@ -640,7 +640,7 @@ private double dirtyPriceFromYieldStandard( first = false; factor = 1; } else { - if (period.isIsRegular()) { + if (period.isRegular()) { factor *= factorOnPeriod; } else { factor *= Math.pow(factorOnPeriod, yearFraction * eventsPerYear); @@ -661,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; @@ -694,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)); } @@ -971,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."); } @@ -1069,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 list, LocalDate date) { diff --git a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java index a2ca53a3e3..b8cde5096f 100644 --- a/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java +++ b/modules/pricer/src/test/java/com/opengamma/strata/pricer/bond/DiscountingFixedCouponBondProductPricerTest.java @@ -230,6 +230,7 @@ public void test_yield_act_act_icma() { double yield = PRICER.yieldFromDirtyPrice(bond, settlementDate, dirtyPrice); assertThat(yield).isCloseTo(0.07288818170674201, offset(TOL)); // 2.x.œœ } + //------------------------------------------------------------------------- @Test public void test_presentValue() { diff --git a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java index 9c2ab84f5e..0aa533fb81 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBond.java @@ -189,7 +189,7 @@ public ResolvedFixedCouponBond resolve(ReferenceData refData) { .currency(currency) .fixedRate(fixedRate) .yearFraction(unadjustedPeriod.yearFraction(dayCount, unadjustedSchedule)) - .isRegular(period.isRegular(accrualSchedule.getFrequency(), accrualSchedule.calculatedRollConvention())) + .regular(period.isRegular(accrualSchedule.getFrequency(), accrualSchedule.calculatedRollConvention())) .build()); } ImmutableList periodicPayments = accrualPeriods.build(); diff --git a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java index 8ed478b96c..6aa02bc0fb 100644 --- a/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java +++ b/modules/product/src/main/java/com/opengamma/strata/product/bond/FixedCouponBondPaymentPeriod.java @@ -20,6 +20,7 @@ import org.joda.beans.MetaProperty; import org.joda.beans.gen.BeanDefinition; import org.joda.beans.gen.ImmutableConstructor; +import org.joda.beans.gen.ImmutableDefaults; import org.joda.beans.gen.PropertyDefinition; import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; import org.joda.beans.impl.direct.DirectMetaBean; @@ -123,16 +124,19 @@ public final class FixedCouponBondPaymentPeriod @PropertyDefinition(validate = "ArgChecker.notNegative") private final double yearFraction; /** - * Indicates if the is period is regular + * Indicates if the is period is regular. *

- * The regular status of the period - *

- * It true the a full coupon is paid. Otherwise the period is shorter/longer + * If true the full coupon is paid, otherwise the period is shorter/longer. */ @PropertyDefinition - private final boolean isRegular; + private final boolean regular; //------------------------------------------------------------------------- + @ImmutableDefaults + private static void applyDefaults(Builder builder) { + builder.regular = true; + } + // could use @ImmutablePreBuild and @ImmutableValidate but faster inline @ImmutableConstructor private FixedCouponBondPaymentPeriod( @@ -145,7 +149,7 @@ private FixedCouponBondPaymentPeriod( LocalDate detachmentDate, double fixedRate, double yearFraction, - boolean isRegular) { + boolean regular) { this.currency = ArgChecker.notNull(currency, "currency"); this.notional = notional; this.startDate = ArgChecker.notNull(startDate, "startDate"); @@ -155,7 +159,7 @@ private FixedCouponBondPaymentPeriod( this.detachmentDate = firstNonNull(detachmentDate, endDate); this.fixedRate = fixedRate; this.yearFraction = yearFraction; - this.isRegular = isRegular; + this.regular = regular; // check for unadjusted must be after firstNonNull ArgChecker.inOrderNotEqual(startDate, endDate, "startDate", "endDate"); ArgChecker.inOrderNotEqual( @@ -352,15 +356,13 @@ public double getYearFraction() { //----------------------------------------------------------------------- /** - * Gets indicates if the is period is regular + * Gets indicates if the is period is regular. *

- * The regular status of the period - *

- * It true the a full coupon is paid. Otherwise the period is shorter/longer + * If true the full coupon is paid, otherwise the period is shorter/longer. * @return the value of the property */ - public boolean isIsRegular() { - return isRegular; + public boolean isRegular() { + return regular; } //----------------------------------------------------------------------- @@ -388,7 +390,7 @@ public boolean equals(Object obj) { JodaBeanUtils.equal(detachmentDate, other.detachmentDate) && JodaBeanUtils.equal(fixedRate, other.fixedRate) && JodaBeanUtils.equal(yearFraction, other.yearFraction) && - (isRegular == other.isRegular); + (regular == other.regular); } return false; } @@ -405,7 +407,7 @@ public int hashCode() { hash = hash * 31 + JodaBeanUtils.hashCode(detachmentDate); hash = hash * 31 + JodaBeanUtils.hashCode(fixedRate); hash = hash * 31 + JodaBeanUtils.hashCode(yearFraction); - hash = hash * 31 + JodaBeanUtils.hashCode(isRegular); + hash = hash * 31 + JodaBeanUtils.hashCode(regular); return hash; } @@ -422,7 +424,7 @@ public String toString() { buf.append("detachmentDate").append('=').append(JodaBeanUtils.toString(detachmentDate)).append(',').append(' '); buf.append("fixedRate").append('=').append(JodaBeanUtils.toString(fixedRate)).append(',').append(' '); buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)).append(',').append(' '); - buf.append("isRegular").append('=').append(JodaBeanUtils.toString(isRegular)); + buf.append("regular").append('=').append(JodaBeanUtils.toString(regular)); buf.append('}'); return buf.toString(); } @@ -483,10 +485,10 @@ public static final class Meta extends DirectMetaBean { private final MetaProperty yearFraction = DirectMetaProperty.ofImmutable( this, "yearFraction", FixedCouponBondPaymentPeriod.class, Double.TYPE); /** - * The meta-property for the {@code isRegular} property. + * The meta-property for the {@code regular} property. */ - private final MetaProperty isRegular = DirectMetaProperty.ofImmutable( - this, "isRegular", FixedCouponBondPaymentPeriod.class, Boolean.TYPE); + private final MetaProperty regular = DirectMetaProperty.ofImmutable( + this, "regular", FixedCouponBondPaymentPeriod.class, Boolean.TYPE); /** * The meta-properties. */ @@ -501,7 +503,7 @@ public static final class Meta extends DirectMetaBean { "detachmentDate", "fixedRate", "yearFraction", - "isRegular"); + "regular"); /** * Restricted constructor. @@ -530,8 +532,8 @@ protected MetaProperty metaPropertyGet(String propertyName) { return fixedRate; case -1731780257: // yearFraction return yearFraction; - case 506685202: // isRegular - return isRegular; + case 1086463900: // regular + return regular; } return super.metaPropertyGet(propertyName); } @@ -625,11 +627,11 @@ public MetaProperty yearFraction() { } /** - * The meta-property for the {@code isRegular} property. + * The meta-property for the {@code regular} property. * @return the meta-property, not null */ - public MetaProperty isRegular() { - return isRegular; + public MetaProperty regular() { + return regular; } //----------------------------------------------------------------------- @@ -654,8 +656,8 @@ protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { return ((FixedCouponBondPaymentPeriod) bean).getFixedRate(); case -1731780257: // yearFraction return ((FixedCouponBondPaymentPeriod) bean).getYearFraction(); - case 506685202: // isRegular - return ((FixedCouponBondPaymentPeriod) bean).isIsRegular(); + case 1086463900: // regular + return ((FixedCouponBondPaymentPeriod) bean).isRegular(); } return super.propertyGet(bean, propertyName, quiet); } @@ -686,12 +688,13 @@ public static final class Builder extends DirectFieldsBeanBuilder - * The regular status of the period + * Sets indicates if the is period is regular. *

- * It true the a full coupon is paid. Otherwise the period is shorter/longer - * @param isRegular the new value + * If true the full coupon is paid, otherwise the period is shorter/longer. + * @param regular the new value * @return this, for chaining, not null */ - public Builder isRegular(boolean isRegular) { - this.isRegular = isRegular; + public Builder regular(boolean regular) { + this.regular = regular; return this; } @@ -961,7 +962,7 @@ public String toString() { buf.append("detachmentDate").append('=').append(JodaBeanUtils.toString(detachmentDate)).append(',').append(' '); buf.append("fixedRate").append('=').append(JodaBeanUtils.toString(fixedRate)).append(',').append(' '); buf.append("yearFraction").append('=').append(JodaBeanUtils.toString(yearFraction)).append(',').append(' '); - buf.append("isRegular").append('=').append(JodaBeanUtils.toString(isRegular)); + buf.append("regular").append('=').append(JodaBeanUtils.toString(regular)); buf.append('}'); return buf.toString(); }